Protect loop set default executor (#37438)
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
parent
f8651d9faa
commit
f49ce5d1b4
21 changed files with 387 additions and 244 deletions
|
@ -1,6 +1,5 @@
|
||||||
"""Start Home Assistant."""
|
"""Start Home Assistant."""
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -8,32 +7,9 @@ import sys
|
||||||
import threading
|
import threading
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import yarl
|
|
||||||
|
|
||||||
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
|
from homeassistant.const import REQUIRED_PYTHON_VER, RESTART_EXIT_CODE, __version__
|
||||||
|
|
||||||
|
|
||||||
def set_loop() -> None:
|
|
||||||
"""Attempt to use different loop."""
|
|
||||||
# pylint: disable=import-outside-toplevel
|
|
||||||
from asyncio.events import BaseDefaultEventLoopPolicy
|
|
||||||
|
|
||||||
if sys.platform == "win32":
|
|
||||||
if hasattr(asyncio, "WindowsProactorEventLoopPolicy"):
|
|
||||||
# pylint: disable=no-member
|
|
||||||
policy = asyncio.WindowsProactorEventLoopPolicy()
|
|
||||||
else:
|
|
||||||
|
|
||||||
class ProactorPolicy(BaseDefaultEventLoopPolicy):
|
|
||||||
"""Event loop policy to create proactor loops."""
|
|
||||||
|
|
||||||
_loop_factory = asyncio.ProactorEventLoop
|
|
||||||
|
|
||||||
policy = ProactorPolicy()
|
|
||||||
|
|
||||||
asyncio.set_event_loop_policy(policy)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_python() -> None:
|
def validate_python() -> None:
|
||||||
"""Validate that the right Python version is running."""
|
"""Validate that the right Python version is running."""
|
||||||
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
|
if sys.version_info[:3] < REQUIRED_PYTHON_VER:
|
||||||
|
@ -240,39 +216,6 @@ def cmdline() -> List[str]:
|
||||||
return [arg for arg in sys.argv if arg != "--daemon"]
|
return [arg for arg in sys.argv if arg != "--daemon"]
|
||||||
|
|
||||||
|
|
||||||
async def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int:
|
|
||||||
"""Set up Home Assistant and run."""
|
|
||||||
# pylint: disable=import-outside-toplevel
|
|
||||||
from homeassistant import bootstrap
|
|
||||||
|
|
||||||
hass = await bootstrap.async_setup_hass(
|
|
||||||
config_dir=config_dir,
|
|
||||||
verbose=args.verbose,
|
|
||||||
log_rotate_days=args.log_rotate_days,
|
|
||||||
log_file=args.log_file,
|
|
||||||
log_no_color=args.log_no_color,
|
|
||||||
skip_pip=args.skip_pip,
|
|
||||||
safe_mode=args.safe_mode,
|
|
||||||
)
|
|
||||||
|
|
||||||
if hass is None:
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if args.open_ui:
|
|
||||||
import webbrowser # pylint: disable=import-outside-toplevel
|
|
||||||
|
|
||||||
if hass.config.api is not None:
|
|
||||||
scheme = "https" if hass.config.api.use_ssl else "http"
|
|
||||||
url = str(
|
|
||||||
yarl.URL.build(
|
|
||||||
scheme=scheme, host="127.0.0.1", port=hass.config.api.port
|
|
||||||
)
|
|
||||||
)
|
|
||||||
hass.add_job(webbrowser.open, url)
|
|
||||||
|
|
||||||
return await hass.async_run()
|
|
||||||
|
|
||||||
|
|
||||||
def try_to_restart() -> None:
|
def try_to_restart() -> None:
|
||||||
"""Attempt to clean up state and start a new Home Assistant instance."""
|
"""Attempt to clean up state and start a new Home Assistant instance."""
|
||||||
# Things should be mostly shut down already at this point, now just try
|
# Things should be mostly shut down already at this point, now just try
|
||||||
|
@ -319,8 +262,6 @@ def main() -> int:
|
||||||
"""Start Home Assistant."""
|
"""Start Home Assistant."""
|
||||||
validate_python()
|
validate_python()
|
||||||
|
|
||||||
set_loop()
|
|
||||||
|
|
||||||
# Run a simple daemon runner process on Windows to handle restarts
|
# Run a simple daemon runner process on Windows to handle restarts
|
||||||
if os.name == "nt" and "--runner" not in sys.argv:
|
if os.name == "nt" and "--runner" not in sys.argv:
|
||||||
nt_args = cmdline() + ["--runner"]
|
nt_args = cmdline() + ["--runner"]
|
||||||
|
@ -353,7 +294,22 @@ def main() -> int:
|
||||||
if args.pid_file:
|
if args.pid_file:
|
||||||
write_pid(args.pid_file)
|
write_pid(args.pid_file)
|
||||||
|
|
||||||
exit_code = asyncio.run(setup_and_run_hass(config_dir, args), debug=args.debug)
|
# pylint: disable=import-outside-toplevel
|
||||||
|
from homeassistant import runner
|
||||||
|
|
||||||
|
runtime_conf = runner.RuntimeConfig(
|
||||||
|
config_dir=config_dir,
|
||||||
|
verbose=args.verbose,
|
||||||
|
log_rotate_days=args.log_rotate_days,
|
||||||
|
log_file=args.log_file,
|
||||||
|
log_no_color=args.log_no_color,
|
||||||
|
skip_pip=args.skip_pip,
|
||||||
|
safe_mode=args.safe_mode,
|
||||||
|
debug=args.debug,
|
||||||
|
open_ui=args.open_ui,
|
||||||
|
)
|
||||||
|
|
||||||
|
exit_code = runner.run(runtime_conf)
|
||||||
if exit_code == RESTART_EXIT_CODE and not args.runner:
|
if exit_code == RESTART_EXIT_CODE and not args.runner:
|
||||||
try_to_restart()
|
try_to_restart()
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,11 @@ import logging.handlers
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from time import monotonic
|
from time import monotonic
|
||||||
from typing import Any, Dict, Optional, Set
|
from typing import TYPE_CHECKING, Any, Dict, Optional, Set
|
||||||
|
|
||||||
from async_timeout import timeout
|
from async_timeout import timeout
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
import yarl
|
||||||
|
|
||||||
from homeassistant import config as conf_util, config_entries, core, loader
|
from homeassistant import config as conf_util, config_entries, core, loader
|
||||||
from homeassistant.components import http
|
from homeassistant.components import http
|
||||||
|
@ -31,6 +32,9 @@ 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.package import async_get_user_site, is_virtual_env
|
||||||
from homeassistant.util.yaml import clear_secret_cache
|
from homeassistant.util.yaml import clear_secret_cache
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .runner import RuntimeConfig
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
ERROR_LOG_FILENAME = "home-assistant.log"
|
ERROR_LOG_FILENAME = "home-assistant.log"
|
||||||
|
@ -66,23 +70,22 @@ STAGE_1_INTEGRATIONS = {
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_hass(
|
async def async_setup_hass(
|
||||||
*,
|
runtime_config: "RuntimeConfig",
|
||||||
config_dir: str,
|
|
||||||
verbose: bool,
|
|
||||||
log_rotate_days: int,
|
|
||||||
log_file: str,
|
|
||||||
log_no_color: bool,
|
|
||||||
skip_pip: bool,
|
|
||||||
safe_mode: bool,
|
|
||||||
) -> Optional[core.HomeAssistant]:
|
) -> Optional[core.HomeAssistant]:
|
||||||
"""Set up Home Assistant."""
|
"""Set up Home Assistant."""
|
||||||
hass = core.HomeAssistant()
|
hass = core.HomeAssistant()
|
||||||
hass.config.config_dir = config_dir
|
hass.config.config_dir = runtime_config.config_dir
|
||||||
|
|
||||||
async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color)
|
async_enable_logging(
|
||||||
|
hass,
|
||||||
|
runtime_config.verbose,
|
||||||
|
runtime_config.log_rotate_days,
|
||||||
|
runtime_config.log_file,
|
||||||
|
runtime_config.log_no_color,
|
||||||
|
)
|
||||||
|
|
||||||
hass.config.skip_pip = skip_pip
|
hass.config.skip_pip = runtime_config.skip_pip
|
||||||
if skip_pip:
|
if runtime_config.skip_pip:
|
||||||
_LOGGER.warning(
|
_LOGGER.warning(
|
||||||
"Skipping pip installation of required modules. This may cause issues"
|
"Skipping pip installation of required modules. This may cause issues"
|
||||||
)
|
)
|
||||||
|
@ -91,10 +94,11 @@ async def async_setup_hass(
|
||||||
_LOGGER.error("Error getting configuration path")
|
_LOGGER.error("Error getting configuration path")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
_LOGGER.info("Config directory: %s", config_dir)
|
_LOGGER.info("Config directory: %s", runtime_config.config_dir)
|
||||||
|
|
||||||
config_dict = None
|
config_dict = None
|
||||||
basic_setup_success = False
|
basic_setup_success = False
|
||||||
|
safe_mode = runtime_config.safe_mode
|
||||||
|
|
||||||
if not safe_mode:
|
if not safe_mode:
|
||||||
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
|
await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass)
|
||||||
|
@ -107,7 +111,7 @@ async def async_setup_hass(
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if not is_virtual_env():
|
if not is_virtual_env():
|
||||||
await async_mount_local_lib_path(config_dir)
|
await async_mount_local_lib_path(runtime_config.config_dir)
|
||||||
|
|
||||||
basic_setup_success = (
|
basic_setup_success = (
|
||||||
await async_from_config_dict(config_dict, hass) is not None
|
await async_from_config_dict(config_dict, hass) is not None
|
||||||
|
@ -153,9 +157,32 @@ async def async_setup_hass(
|
||||||
{"safe_mode": {}, "http": http_conf}, hass,
|
{"safe_mode": {}, "http": http_conf}, hass,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if runtime_config.open_ui:
|
||||||
|
hass.add_job(open_hass_ui, hass)
|
||||||
|
|
||||||
return hass
|
return hass
|
||||||
|
|
||||||
|
|
||||||
|
def open_hass_ui(hass: core.HomeAssistant) -> None:
|
||||||
|
"""Open the UI."""
|
||||||
|
import webbrowser # pylint: disable=import-outside-toplevel
|
||||||
|
|
||||||
|
if hass.config.api is None or "frontend" not in hass.config.components:
|
||||||
|
_LOGGER.warning("Cannot launch the UI because frontend not loaded")
|
||||||
|
return
|
||||||
|
|
||||||
|
scheme = "https" if hass.config.api.use_ssl else "http"
|
||||||
|
url = str(
|
||||||
|
yarl.URL.build(scheme=scheme, host="127.0.0.1", port=hass.config.api.port)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not webbrowser.open(url):
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Unable to open the Home Assistant UI in a browser. Open it yourself at %s",
|
||||||
|
url,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def async_from_config_dict(
|
async def async_from_config_dict(
|
||||||
config: ConfigType, hass: core.HomeAssistant
|
config: ConfigType, hass: core.HomeAssistant
|
||||||
) -> Optional[core.HomeAssistant]:
|
) -> Optional[core.HomeAssistant]:
|
||||||
|
|
|
@ -7,8 +7,8 @@ import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow
|
from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow
|
||||||
from homeassistant.const import CONF_HOST, CONF_PORT
|
from homeassistant.const import CONF_HOST, CONF_PORT
|
||||||
from homeassistant.helpers import ConfigType
|
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import ( # pylint:disable=unused-import
|
from .const import ( # pylint:disable=unused-import
|
||||||
CONF_DEVICE_IDENT,
|
CONF_DEVICE_IDENT,
|
||||||
|
|
|
@ -8,7 +8,7 @@ import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries
|
from homeassistant import config_entries
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.helpers import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import DOMAIN # pylint: disable=unused-import
|
from .const import DOMAIN # pylint: disable=unused-import
|
||||||
from .utils import load_plum
|
from .utils import load_plum
|
||||||
|
|
|
@ -5,7 +5,6 @@ Home Assistant is a Home Automation framework for observing the state
|
||||||
of entities and react to changes.
|
of entities and react to changes.
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
|
||||||
import datetime
|
import datetime
|
||||||
import enum
|
import enum
|
||||||
import functools
|
import functools
|
||||||
|
@ -145,19 +144,6 @@ def is_callback(func: Callable[..., Any]) -> bool:
|
||||||
return getattr(func, "_hass_callback", False) is True
|
return getattr(func, "_hass_callback", False) is True
|
||||||
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def async_loop_exception_handler(_: Any, context: Dict) -> None:
|
|
||||||
"""Handle all exception inside the core loop."""
|
|
||||||
kwargs = {}
|
|
||||||
exception = context.get("exception")
|
|
||||||
if exception:
|
|
||||||
kwargs["exc_info"] = (type(exception), exception, exception.__traceback__)
|
|
||||||
|
|
||||||
_LOGGER.error(
|
|
||||||
"Error doing job: %s", context["message"], **kwargs # type: ignore
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CoreState(enum.Enum):
|
class CoreState(enum.Enum):
|
||||||
"""Represent the current state of Home Assistant."""
|
"""Represent the current state of Home Assistant."""
|
||||||
|
|
||||||
|
@ -179,18 +165,9 @@ class HomeAssistant:
|
||||||
http: "HomeAssistantHTTP" = None # type: ignore
|
http: "HomeAssistantHTTP" = None # type: ignore
|
||||||
config_entries: "ConfigEntries" = None # type: ignore
|
config_entries: "ConfigEntries" = None # type: ignore
|
||||||
|
|
||||||
def __init__(self, loop: Optional[asyncio.events.AbstractEventLoop] = None) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize new Home Assistant object."""
|
"""Initialize new Home Assistant object."""
|
||||||
self.loop: asyncio.events.AbstractEventLoop = (loop or asyncio.get_event_loop())
|
self.loop = asyncio.get_running_loop()
|
||||||
|
|
||||||
executor_opts: Dict[str, Any] = {
|
|
||||||
"max_workers": None,
|
|
||||||
"thread_name_prefix": "SyncWorker",
|
|
||||||
}
|
|
||||||
|
|
||||||
self.executor = ThreadPoolExecutor(**executor_opts)
|
|
||||||
self.loop.set_default_executor(self.executor)
|
|
||||||
self.loop.set_exception_handler(async_loop_exception_handler)
|
|
||||||
self._pending_tasks: list = []
|
self._pending_tasks: list = []
|
||||||
self._track_task = True
|
self._track_task = True
|
||||||
self.bus = EventBus(self)
|
self.bus = EventBus(self)
|
||||||
|
@ -461,7 +438,9 @@ class HomeAssistant:
|
||||||
self.state = CoreState.not_running
|
self.state = CoreState.not_running
|
||||||
self.bus.async_fire(EVENT_HOMEASSISTANT_CLOSE)
|
self.bus.async_fire(EVENT_HOMEASSISTANT_CLOSE)
|
||||||
await self.async_block_till_done()
|
await self.async_block_till_done()
|
||||||
self.executor.shutdown()
|
|
||||||
|
# Python 3.9+ and backported in runner.py
|
||||||
|
await self.loop.shutdown_default_executor() # type: ignore
|
||||||
|
|
||||||
self.exit_code = exit_code
|
self.exit_code = exit_code
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
"""Helper methods for components within Home Assistant."""
|
"""Helper methods for components within Home Assistant."""
|
||||||
import re
|
import re
|
||||||
from typing import Any, Iterable, Sequence, Tuple
|
from typing import TYPE_CHECKING, Any, Iterable, Sequence, Tuple
|
||||||
|
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_PLATFORM
|
||||||
|
|
||||||
from .typing import ConfigType
|
if TYPE_CHECKING:
|
||||||
|
from .typing import ConfigType
|
||||||
|
|
||||||
|
|
||||||
def config_per_platform(config: ConfigType, domain: str) -> Iterable[Tuple[Any, Any]]:
|
def config_per_platform(config: "ConfigType", domain: str) -> Iterable[Tuple[Any, Any]]:
|
||||||
"""Break a component config into different platforms.
|
"""Break a component config into different platforms.
|
||||||
|
|
||||||
For example, will find 'switch', 'switch 2', 'switch 3', .. etc
|
For example, will find 'switch', 'switch 2', 'switch 3', .. etc
|
||||||
|
@ -31,7 +32,7 @@ def config_per_platform(config: ConfigType, domain: str) -> Iterable[Tuple[Any,
|
||||||
yield platform, item
|
yield platform, item
|
||||||
|
|
||||||
|
|
||||||
def extract_domain_configs(config: ConfigType, domain: str) -> Sequence[str]:
|
def extract_domain_configs(config: "ConfigType", domain: str) -> Sequence[str]:
|
||||||
"""Extract keys from config for given domain name.
|
"""Extract keys from config for given domain name.
|
||||||
|
|
||||||
Async friendly.
|
Async friendly.
|
||||||
|
|
|
@ -13,7 +13,7 @@ import async_timeout
|
||||||
|
|
||||||
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__
|
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE, __version__
|
||||||
from homeassistant.core import Event, callback
|
from homeassistant.core import Event, callback
|
||||||
from homeassistant.helpers.frame import MissingIntegrationFrame, get_integration_frame
|
from homeassistant.helpers.frame import warn_use
|
||||||
from homeassistant.helpers.typing import HomeAssistantType
|
from homeassistant.helpers.typing import HomeAssistantType
|
||||||
from homeassistant.loader import bind_hass
|
from homeassistant.loader import bind_hass
|
||||||
from homeassistant.util import ssl as ssl_util
|
from homeassistant.util import ssl as ssl_util
|
||||||
|
@ -71,34 +71,9 @@ def async_create_clientsession(
|
||||||
connector=connector, headers={USER_AGENT: SERVER_SOFTWARE}, **kwargs,
|
connector=connector, headers={USER_AGENT: SERVER_SOFTWARE}, **kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def patched_close() -> None:
|
clientsession.close = warn_use( # type: ignore
|
||||||
"""Mock close to avoid integrations closing our session."""
|
clientsession.close, "closes the Home Assistant aiohttp session"
|
||||||
try:
|
)
|
||||||
found_frame, integration, path = get_integration_frame()
|
|
||||||
except MissingIntegrationFrame:
|
|
||||||
# Did not source from an integration? Hard error.
|
|
||||||
raise RuntimeError(
|
|
||||||
"Detected closing of the Home Assistant aiohttp session in the Home Assistant core. "
|
|
||||||
"Please report this issue."
|
|
||||||
)
|
|
||||||
|
|
||||||
index = found_frame.filename.index(path)
|
|
||||||
if path == "custom_components/":
|
|
||||||
extra = " to the custom component author"
|
|
||||||
else:
|
|
||||||
extra = ""
|
|
||||||
|
|
||||||
_LOGGER.warning(
|
|
||||||
"Detected integration that closes the Home Assistant aiohttp session. "
|
|
||||||
"Please report issue%s for %s using this method at %s, line %s: %s",
|
|
||||||
extra,
|
|
||||||
integration,
|
|
||||||
found_frame.filename[index:],
|
|
||||||
found_frame.lineno,
|
|
||||||
found_frame.line.strip(),
|
|
||||||
)
|
|
||||||
|
|
||||||
clientsession.close = patched_close # type: ignore
|
|
||||||
|
|
||||||
if auto_cleanup:
|
if auto_cleanup:
|
||||||
_async_register_clientsession_shutdown(hass, clientsession)
|
_async_register_clientsession_shutdown(hass, clientsession)
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
"""Provide frame helper for finding the current frame context."""
|
"""Provide frame helper for finding the current frame context."""
|
||||||
|
import asyncio
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
from traceback import FrameSummary, extract_stack
|
from traceback import FrameSummary, extract_stack
|
||||||
from typing import Tuple
|
from typing import Any, Callable, Tuple, TypeVar, cast
|
||||||
|
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
CALLABLE_T = TypeVar("CALLABLE_T", bound=Callable) # pylint: disable=invalid-name
|
||||||
|
|
||||||
|
|
||||||
def get_integration_frame() -> Tuple[FrameSummary, str, str]:
|
def get_integration_frame() -> Tuple[FrameSummary, str, str]:
|
||||||
"""Return the frame, integration and integration path of the current stack frame."""
|
"""Return the frame, integration and integration path of the current stack frame."""
|
||||||
|
@ -34,3 +41,49 @@ def get_integration_frame() -> Tuple[FrameSummary, str, str]:
|
||||||
|
|
||||||
class MissingIntegrationFrame(HomeAssistantError):
|
class MissingIntegrationFrame(HomeAssistantError):
|
||||||
"""Raised when no integration is found in the frame."""
|
"""Raised when no integration is found in the frame."""
|
||||||
|
|
||||||
|
|
||||||
|
def report(what: str) -> None:
|
||||||
|
"""Report incorrect usage.
|
||||||
|
|
||||||
|
Async friendly.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
found_frame, integration, path = get_integration_frame()
|
||||||
|
except MissingIntegrationFrame:
|
||||||
|
# Did not source from an integration? Hard error.
|
||||||
|
raise RuntimeError(f"Detected code that {what}. Please report this issue.")
|
||||||
|
|
||||||
|
index = found_frame.filename.index(path)
|
||||||
|
if path == "custom_components/":
|
||||||
|
extra = " to the custom component author"
|
||||||
|
else:
|
||||||
|
extra = ""
|
||||||
|
|
||||||
|
_LOGGER.warning(
|
||||||
|
"Detected integration that %s. "
|
||||||
|
"Please report issue%s for %s using this method at %s, line %s: %s",
|
||||||
|
what,
|
||||||
|
extra,
|
||||||
|
integration,
|
||||||
|
found_frame.filename[index:],
|
||||||
|
found_frame.lineno,
|
||||||
|
found_frame.line.strip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def warn_use(func: CALLABLE_T, what: str) -> CALLABLE_T:
|
||||||
|
"""Mock a function to warn when it was about to be used."""
|
||||||
|
if asyncio.iscoroutinefunction(func):
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
async def report_use(*args: Any, **kwargs: Any) -> None:
|
||||||
|
report(what)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
@functools.wraps(func)
|
||||||
|
def report_use(*args: Any, **kwargs: Any) -> None:
|
||||||
|
report(what)
|
||||||
|
|
||||||
|
return cast(CALLABLE_T, report_use)
|
||||||
|
|
119
homeassistant/runner.py
Normal file
119
homeassistant/runner.py
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
"""Run Home Assistant."""
|
||||||
|
import asyncio
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
import dataclasses
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
from homeassistant import bootstrap
|
||||||
|
from homeassistant.core import callback
|
||||||
|
from homeassistant.helpers.frame import warn_use
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class RuntimeConfig:
|
||||||
|
"""Class to hold the information for running Home Assistant."""
|
||||||
|
|
||||||
|
config_dir: str
|
||||||
|
skip_pip: bool = False
|
||||||
|
safe_mode: bool = False
|
||||||
|
|
||||||
|
verbose: bool = False
|
||||||
|
|
||||||
|
log_rotate_days: Optional[int] = None
|
||||||
|
log_file: Optional[str] = None
|
||||||
|
log_no_color: bool = False
|
||||||
|
|
||||||
|
debug: bool = False
|
||||||
|
open_ui: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
# In Python 3.8+ proactor policy is the default on Windows
|
||||||
|
if sys.platform == "win32" and sys.version_info[:2] < (3, 8):
|
||||||
|
PolicyBase = asyncio.WindowsProactorEventLoopPolicy
|
||||||
|
else:
|
||||||
|
PolicyBase = asyncio.DefaultEventLoopPolicy # pylint: disable=invalid-name
|
||||||
|
|
||||||
|
|
||||||
|
class HassEventLoopPolicy(PolicyBase):
|
||||||
|
"""Event loop policy for Home Assistant."""
|
||||||
|
|
||||||
|
def __init__(self, debug: bool) -> None:
|
||||||
|
"""Init the event loop policy."""
|
||||||
|
super().__init__()
|
||||||
|
self.debug = debug
|
||||||
|
|
||||||
|
@property
|
||||||
|
def loop_name(self) -> str:
|
||||||
|
"""Return name of the loop."""
|
||||||
|
return self._loop_factory.__name__
|
||||||
|
|
||||||
|
def new_event_loop(self):
|
||||||
|
"""Get the event loop."""
|
||||||
|
loop = super().new_event_loop()
|
||||||
|
loop.set_exception_handler(_async_loop_exception_handler)
|
||||||
|
if self.debug:
|
||||||
|
loop.set_debug(True)
|
||||||
|
|
||||||
|
executor = ThreadPoolExecutor(thread_name_prefix="SyncWorker")
|
||||||
|
loop.set_default_executor(executor)
|
||||||
|
loop.set_default_executor = warn_use( # type: ignore
|
||||||
|
loop.set_default_executor, "sets default executor on the event loop"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Python 3.9+
|
||||||
|
if hasattr(loop, "shutdown_default_executor"):
|
||||||
|
return loop
|
||||||
|
|
||||||
|
# Copied from Python 3.9 source
|
||||||
|
def _do_shutdown(future):
|
||||||
|
try:
|
||||||
|
executor.shutdown(wait=True)
|
||||||
|
loop.call_soon_threadsafe(future.set_result, None)
|
||||||
|
except Exception as ex: # pylint: disable=broad-except
|
||||||
|
loop.call_soon_threadsafe(future.set_exception, ex)
|
||||||
|
|
||||||
|
async def shutdown_default_executor():
|
||||||
|
"""Schedule the shutdown of the default executor."""
|
||||||
|
future = loop.create_future()
|
||||||
|
thread = threading.Thread(target=_do_shutdown, args=(future,))
|
||||||
|
thread.start()
|
||||||
|
try:
|
||||||
|
await future
|
||||||
|
finally:
|
||||||
|
thread.join()
|
||||||
|
|
||||||
|
loop.shutdown_default_executor = shutdown_default_executor
|
||||||
|
|
||||||
|
return loop
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _async_loop_exception_handler(self, _: Any, context: Dict) -> None:
|
||||||
|
"""Handle all exception inside the core loop."""
|
||||||
|
kwargs = {}
|
||||||
|
exception = context.get("exception")
|
||||||
|
if exception:
|
||||||
|
kwargs["exc_info"] = (type(exception), exception, exception.__traceback__)
|
||||||
|
|
||||||
|
logging.getLogger(__package__).error(
|
||||||
|
"Error doing job: %s", context["message"], **kwargs # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_and_run_hass(runtime_config: RuntimeConfig,) -> int:
|
||||||
|
"""Set up Home Assistant and run."""
|
||||||
|
hass = await bootstrap.async_setup_hass(runtime_config)
|
||||||
|
|
||||||
|
if hass is None:
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return await hass.async_run()
|
||||||
|
|
||||||
|
|
||||||
|
def run(runtime_config: RuntimeConfig) -> int:
|
||||||
|
"""Run Home Assistant."""
|
||||||
|
asyncio.set_event_loop_policy(HassEventLoopPolicy(runtime_config.debug))
|
||||||
|
return asyncio.run(setup_and_run_hass(runtime_config))
|
|
@ -7,6 +7,7 @@ import os
|
||||||
import sys
|
import sys
|
||||||
from typing import List, Optional, Sequence, Text
|
from typing import List, Optional, Sequence, Text
|
||||||
|
|
||||||
|
from homeassistant import runner
|
||||||
from homeassistant.bootstrap import async_mount_local_lib_path
|
from homeassistant.bootstrap import async_mount_local_lib_path
|
||||||
from homeassistant.config import get_default_config_dir
|
from homeassistant.config import get_default_config_dir
|
||||||
from homeassistant.requirements import pip_kwargs
|
from homeassistant.requirements import pip_kwargs
|
||||||
|
@ -59,6 +60,8 @@ def run(args: List) -> int:
|
||||||
print("Aborting script, could not install dependency", req)
|
print("Aborting script, could not install dependency", req)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
asyncio.set_event_loop_policy(runner.HassEventLoopPolicy(False))
|
||||||
|
|
||||||
return script.run(args[1:]) # type: ignore
|
return script.run(args[1:]) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from homeassistant import runner
|
||||||
from homeassistant.auth import auth_manager_from_config
|
from homeassistant.auth import auth_manager_from_config
|
||||||
from homeassistant.auth.providers import homeassistant as hass_auth
|
from homeassistant.auth.providers import homeassistant as hass_auth
|
||||||
from homeassistant.config import get_default_config_dir
|
from homeassistant.config import get_default_config_dir
|
||||||
|
@ -43,24 +44,24 @@ def run(args):
|
||||||
parser_change_pw.add_argument("new_password", type=str)
|
parser_change_pw.add_argument("new_password", type=str)
|
||||||
parser_change_pw.set_defaults(func=change_password)
|
parser_change_pw.set_defaults(func=change_password)
|
||||||
|
|
||||||
args = parser.parse_args(args)
|
asyncio.set_event_loop_policy(runner.HassEventLoopPolicy(False))
|
||||||
loop = asyncio.get_event_loop()
|
asyncio.run(run_command(parser.parse_args(args)))
|
||||||
hass = HomeAssistant(loop=loop)
|
|
||||||
loop.run_until_complete(run_command(hass, args))
|
|
||||||
|
|
||||||
# Triggers save on used storage helpers with delay (core auth)
|
|
||||||
logging.getLogger("homeassistant.core").setLevel(logging.WARNING)
|
|
||||||
loop.run_until_complete(hass.async_stop())
|
|
||||||
|
|
||||||
|
|
||||||
async def run_command(hass, args):
|
async def run_command(args):
|
||||||
"""Run the command."""
|
"""Run the command."""
|
||||||
|
hass = HomeAssistant()
|
||||||
hass.config.config_dir = os.path.join(os.getcwd(), args.config)
|
hass.config.config_dir = os.path.join(os.getcwd(), args.config)
|
||||||
hass.auth = await auth_manager_from_config(hass, [{"type": "homeassistant"}], [])
|
hass.auth = await auth_manager_from_config(hass, [{"type": "homeassistant"}], [])
|
||||||
provider = hass.auth.auth_providers[0]
|
provider = hass.auth.auth_providers[0]
|
||||||
await provider.async_initialize()
|
await provider.async_initialize()
|
||||||
await args.func(hass, provider, args)
|
await args.func(hass, provider, args)
|
||||||
|
|
||||||
|
# Triggers save on used storage helpers with delay (core auth)
|
||||||
|
logging.getLogger("homeassistant.core").setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
await hass.async_stop()
|
||||||
|
|
||||||
|
|
||||||
async def list_users(hass, provider, args):
|
async def list_users(hass, provider, args):
|
||||||
"""List the users."""
|
"""List the users."""
|
||||||
|
|
|
@ -36,18 +36,19 @@ def run(args):
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
bench = BENCHMARKS[args.name]
|
bench = BENCHMARKS[args.name]
|
||||||
|
print("Using event loop:", asyncio.get_event_loop_policy().loop_name)
|
||||||
print("Using event loop:", asyncio.get_event_loop_policy().__module__)
|
|
||||||
|
|
||||||
with suppress(KeyboardInterrupt):
|
with suppress(KeyboardInterrupt):
|
||||||
while True:
|
while True:
|
||||||
loop = asyncio.new_event_loop()
|
asyncio.run(run_benchmark(bench))
|
||||||
hass = core.HomeAssistant(loop)
|
|
||||||
hass.async_stop_track_tasks()
|
|
||||||
runtime = loop.run_until_complete(bench(hass))
|
async def run_benchmark(bench):
|
||||||
print(f"Benchmark {bench.__name__} done in {runtime}s")
|
"""Run a benchmark."""
|
||||||
loop.run_until_complete(hass.async_stop())
|
hass = core.HomeAssistant()
|
||||||
loop.close()
|
runtime = await bench(hass)
|
||||||
|
print(f"Benchmark {bench.__name__} done in {runtime}s")
|
||||||
|
await hass.async_stop()
|
||||||
|
|
||||||
|
|
||||||
def benchmark(func: CALLABLE_T) -> CALLABLE_T:
|
def benchmark(func: CALLABLE_T) -> CALLABLE_T:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Script to check the configuration file."""
|
"""Script to check the configuration file."""
|
||||||
import argparse
|
import argparse
|
||||||
|
import asyncio
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from collections.abc import Mapping, Sequence
|
from collections.abc import Mapping, Sequence
|
||||||
from glob import glob
|
from glob import glob
|
||||||
|
@ -199,12 +200,7 @@ def check(config_dir, secrets=False):
|
||||||
yaml_loader.yaml.SafeLoader.add_constructor("!secret", yaml_loader.secret_yaml)
|
yaml_loader.yaml.SafeLoader.add_constructor("!secret", yaml_loader.secret_yaml)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hass = core.HomeAssistant()
|
res["components"] = asyncio.run(async_check_config(config_dir))
|
||||||
hass.config.config_dir = config_dir
|
|
||||||
|
|
||||||
res["components"] = hass.loop.run_until_complete(
|
|
||||||
async_check_ha_config_file(hass)
|
|
||||||
)
|
|
||||||
res["secret_cache"] = OrderedDict(yaml_loader.__SECRET_CACHE)
|
res["secret_cache"] = OrderedDict(yaml_loader.__SECRET_CACHE)
|
||||||
for err in res["components"].errors:
|
for err in res["components"].errors:
|
||||||
domain = err.domain or ERROR_STR
|
domain = err.domain or ERROR_STR
|
||||||
|
@ -213,7 +209,6 @@ def check(config_dir, secrets=False):
|
||||||
res["except"].setdefault(domain, []).append(err.config)
|
res["except"].setdefault(domain, []).append(err.config)
|
||||||
|
|
||||||
except Exception as err: # pylint: disable=broad-except
|
except Exception as err: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("BURB")
|
|
||||||
print(color("red", "Fatal error while loading config:"), str(err))
|
print(color("red", "Fatal error while loading config:"), str(err))
|
||||||
res["except"].setdefault(ERROR_STR, []).append(str(err))
|
res["except"].setdefault(ERROR_STR, []).append(str(err))
|
||||||
finally:
|
finally:
|
||||||
|
@ -230,6 +225,15 @@ def check(config_dir, secrets=False):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
async def async_check_config(config_dir):
|
||||||
|
"""Check the HA config."""
|
||||||
|
hass = core.HomeAssistant()
|
||||||
|
hass.config.config_dir = config_dir
|
||||||
|
components = await async_check_ha_config_file(hass)
|
||||||
|
await hass.async_stop(force=True)
|
||||||
|
return components
|
||||||
|
|
||||||
|
|
||||||
def line_info(obj, **kwargs):
|
def line_info(obj, **kwargs):
|
||||||
"""Display line config source."""
|
"""Display line config source."""
|
||||||
if hasattr(obj, "__config_file__"):
|
if hasattr(obj, "__config_file__"):
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
"""Script to ensure a configuration file exists."""
|
"""Script to ensure a configuration file exists."""
|
||||||
import argparse
|
import argparse
|
||||||
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import homeassistant.config as config_util
|
import homeassistant.config as config_util
|
||||||
|
@ -31,15 +32,15 @@ def run(args):
|
||||||
print("Creating directory", config_dir)
|
print("Creating directory", config_dir)
|
||||||
os.makedirs(config_dir)
|
os.makedirs(config_dir)
|
||||||
|
|
||||||
hass = HomeAssistant()
|
config_path = asyncio.run(async_run(config_dir))
|
||||||
hass.config.config_dir = config_dir
|
|
||||||
config_path = hass.loop.run_until_complete(async_run(hass))
|
|
||||||
print("Configuration file:", config_path)
|
print("Configuration file:", config_path)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
async def async_run(hass):
|
async def async_run(config_dir):
|
||||||
"""Make sure config exists."""
|
"""Make sure config exists."""
|
||||||
|
hass = HomeAssistant()
|
||||||
|
hass.config.config_dir = config_dir
|
||||||
path = await config_util.async_ensure_config_exists(hass)
|
path = await config_util.async_ensure_config_exists(hass)
|
||||||
await hass.async_stop(force=True)
|
await hass.async_stop(force=True)
|
||||||
return path
|
return path
|
||||||
|
|
|
@ -149,7 +149,7 @@ def get_test_home_assistant():
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
async def async_test_home_assistant(loop):
|
async def async_test_home_assistant(loop):
|
||||||
"""Return a Home Assistant object pointing at test config dir."""
|
"""Return a Home Assistant object pointing at test config dir."""
|
||||||
hass = ha.HomeAssistant(loop)
|
hass = ha.HomeAssistant()
|
||||||
store = auth_store.AuthStore(hass)
|
store = auth_store.AuthStore(hass)
|
||||||
hass.auth = auth.AuthManager(hass, store, {}, {})
|
hass.auth = auth.AuthManager(hass, store, {}, {})
|
||||||
ensure_auth_manager_loaded(hass.auth)
|
ensure_auth_manager_loaded(hass.auth)
|
||||||
|
|
|
@ -8,8 +8,8 @@ from homeassistant.core import callback as ha_callback
|
||||||
from tests.async_mock import patch
|
from tests.async_mock import patch
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture
|
||||||
def hk_driver():
|
def hk_driver(loop):
|
||||||
"""Return a custom AccessoryDriver instance for HomeKit accessory init."""
|
"""Return a custom AccessoryDriver instance for HomeKit accessory init."""
|
||||||
with patch("pyhap.accessory_driver.Zeroconf"), patch(
|
with patch("pyhap.accessory_driver.Zeroconf"), patch(
|
||||||
"pyhap.accessory_driver.AccessoryEncoder"
|
"pyhap.accessory_driver.AccessoryEncoder"
|
||||||
|
@ -18,7 +18,7 @@ def hk_driver():
|
||||||
), patch(
|
), patch(
|
||||||
"pyhap.accessory_driver.AccessoryDriver.persist"
|
"pyhap.accessory_driver.AccessoryDriver.persist"
|
||||||
):
|
):
|
||||||
yield AccessoryDriver(pincode=b"123-45-678", address="127.0.0.1")
|
yield AccessoryDriver(pincode=b"123-45-678", address="127.0.0.1", loop=loop)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
|
|
@ -68,6 +68,11 @@ from tests.components.homekit.common import patch_debounce
|
||||||
IP_ADDRESS = "127.0.0.1"
|
IP_ADDRESS = "127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def always_patch_driver(hk_driver):
|
||||||
|
"""Load the hk_driver fixture."""
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="device_reg")
|
@pytest.fixture(name="device_reg")
|
||||||
def device_reg_fixture(hass):
|
def device_reg_fixture(hass):
|
||||||
"""Return an empty, loaded, registry."""
|
"""Return an empty, loaded, registry."""
|
||||||
|
|
|
@ -215,7 +215,7 @@ def test_density_to_air_quality():
|
||||||
assert density_to_air_quality(300) == 5
|
assert density_to_air_quality(300) == 5
|
||||||
|
|
||||||
|
|
||||||
async def test_show_setup_msg(hass):
|
async def test_show_setup_msg(hass, hk_driver):
|
||||||
"""Test show setup message as persistence notification."""
|
"""Test show setup message as persistence notification."""
|
||||||
pincode = b"123-45-678"
|
pincode = b"123-45-678"
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
"""Set up some common test helper things."""
|
"""Set up some common test helper things."""
|
||||||
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests_mock as _requests_mock
|
import requests_mock as _requests_mock
|
||||||
|
|
||||||
from homeassistant import core as ha, loader, util
|
from homeassistant import core as ha, loader, runner, util
|
||||||
from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY
|
from homeassistant.auth.const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY
|
||||||
from homeassistant.auth.providers import homeassistant, legacy_api_password
|
from homeassistant.auth.providers import homeassistant, legacy_api_password
|
||||||
from homeassistant.components import mqtt
|
from homeassistant.components import mqtt
|
||||||
|
@ -40,6 +41,10 @@ from tests.test_util.aiohttp import mock_aiohttp_client # noqa: E402, isort:ski
|
||||||
logging.basicConfig(level=logging.DEBUG)
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
|
logging.getLogger("sqlalchemy.engine").setLevel(logging.INFO)
|
||||||
|
|
||||||
|
asyncio.set_event_loop_policy(runner.HassEventLoopPolicy(False))
|
||||||
|
# Disable fixtures overriding our beautiful policy
|
||||||
|
asyncio.set_event_loop_policy = lambda policy: None
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
"""Register marker for tests that log exceptions."""
|
"""Register marker for tests that log exceptions."""
|
||||||
|
|
|
@ -7,17 +7,15 @@ from unittest.mock import Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant import bootstrap, core
|
from homeassistant import bootstrap, core, runner
|
||||||
import homeassistant.config as config_util
|
import homeassistant.config as config_util
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
from tests.async_mock import patch
|
from tests.async_mock import patch
|
||||||
from tests.common import (
|
from tests.common import (
|
||||||
MockConfigEntry,
|
|
||||||
MockModule,
|
MockModule,
|
||||||
MockPlatform,
|
MockPlatform,
|
||||||
flush_store,
|
|
||||||
get_test_config_dir,
|
get_test_config_dir,
|
||||||
mock_coro,
|
mock_coro,
|
||||||
mock_entity_platform,
|
mock_entity_platform,
|
||||||
|
@ -351,6 +349,7 @@ async def test_setup_hass(
|
||||||
mock_ensure_config_exists,
|
mock_ensure_config_exists,
|
||||||
mock_process_ha_config_upgrade,
|
mock_process_ha_config_upgrade,
|
||||||
caplog,
|
caplog,
|
||||||
|
loop,
|
||||||
):
|
):
|
||||||
"""Test it works."""
|
"""Test it works."""
|
||||||
verbose = Mock()
|
verbose = Mock()
|
||||||
|
@ -365,13 +364,15 @@ async def test_setup_hass(
|
||||||
"homeassistant.components.http.start_http_server_and_save_config"
|
"homeassistant.components.http.start_http_server_and_save_config"
|
||||||
):
|
):
|
||||||
hass = await bootstrap.async_setup_hass(
|
hass = await bootstrap.async_setup_hass(
|
||||||
config_dir=get_test_config_dir(),
|
runner.RuntimeConfig(
|
||||||
verbose=verbose,
|
config_dir=get_test_config_dir(),
|
||||||
log_rotate_days=log_rotate_days,
|
verbose=verbose,
|
||||||
log_file=log_file,
|
log_rotate_days=log_rotate_days,
|
||||||
log_no_color=log_no_color,
|
log_file=log_file,
|
||||||
skip_pip=True,
|
log_no_color=log_no_color,
|
||||||
safe_mode=False,
|
skip_pip=True,
|
||||||
|
safe_mode=False,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "Waiting on integrations to complete setup" not in caplog.text
|
assert "Waiting on integrations to complete setup" not in caplog.text
|
||||||
|
@ -399,6 +400,7 @@ async def test_setup_hass_takes_longer_than_log_slow_startup(
|
||||||
mock_ensure_config_exists,
|
mock_ensure_config_exists,
|
||||||
mock_process_ha_config_upgrade,
|
mock_process_ha_config_upgrade,
|
||||||
caplog,
|
caplog,
|
||||||
|
loop,
|
||||||
):
|
):
|
||||||
"""Test it works."""
|
"""Test it works."""
|
||||||
verbose = Mock()
|
verbose = Mock()
|
||||||
|
@ -420,13 +422,15 @@ async def test_setup_hass_takes_longer_than_log_slow_startup(
|
||||||
"homeassistant.components.http.start_http_server_and_save_config"
|
"homeassistant.components.http.start_http_server_and_save_config"
|
||||||
):
|
):
|
||||||
await bootstrap.async_setup_hass(
|
await bootstrap.async_setup_hass(
|
||||||
config_dir=get_test_config_dir(),
|
runner.RuntimeConfig(
|
||||||
verbose=verbose,
|
config_dir=get_test_config_dir(),
|
||||||
log_rotate_days=log_rotate_days,
|
verbose=verbose,
|
||||||
log_file=log_file,
|
log_rotate_days=log_rotate_days,
|
||||||
log_no_color=log_no_color,
|
log_file=log_file,
|
||||||
skip_pip=True,
|
log_no_color=log_no_color,
|
||||||
safe_mode=False,
|
skip_pip=True,
|
||||||
|
safe_mode=False,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "Waiting on integrations to complete setup" in caplog.text
|
assert "Waiting on integrations to complete setup" in caplog.text
|
||||||
|
@ -438,19 +442,22 @@ async def test_setup_hass_invalid_yaml(
|
||||||
mock_mount_local_lib_path,
|
mock_mount_local_lib_path,
|
||||||
mock_ensure_config_exists,
|
mock_ensure_config_exists,
|
||||||
mock_process_ha_config_upgrade,
|
mock_process_ha_config_upgrade,
|
||||||
|
loop,
|
||||||
):
|
):
|
||||||
"""Test it works."""
|
"""Test it works."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.config.async_hass_config_yaml", side_effect=HomeAssistantError
|
"homeassistant.config.async_hass_config_yaml", side_effect=HomeAssistantError
|
||||||
), patch("homeassistant.components.http.start_http_server_and_save_config"):
|
), patch("homeassistant.components.http.start_http_server_and_save_config"):
|
||||||
hass = await bootstrap.async_setup_hass(
|
hass = await bootstrap.async_setup_hass(
|
||||||
config_dir=get_test_config_dir(),
|
runner.RuntimeConfig(
|
||||||
verbose=False,
|
config_dir=get_test_config_dir(),
|
||||||
log_rotate_days=10,
|
verbose=False,
|
||||||
log_file="",
|
log_rotate_days=10,
|
||||||
log_no_color=False,
|
log_file="",
|
||||||
skip_pip=True,
|
log_no_color=False,
|
||||||
safe_mode=False,
|
skip_pip=True,
|
||||||
|
safe_mode=False,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "safe_mode" in hass.config.components
|
assert "safe_mode" in hass.config.components
|
||||||
|
@ -463,49 +470,52 @@ async def test_setup_hass_config_dir_nonexistent(
|
||||||
mock_mount_local_lib_path,
|
mock_mount_local_lib_path,
|
||||||
mock_ensure_config_exists,
|
mock_ensure_config_exists,
|
||||||
mock_process_ha_config_upgrade,
|
mock_process_ha_config_upgrade,
|
||||||
|
loop,
|
||||||
):
|
):
|
||||||
"""Test it works."""
|
"""Test it works."""
|
||||||
mock_ensure_config_exists.return_value = False
|
mock_ensure_config_exists.return_value = False
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
await bootstrap.async_setup_hass(
|
await bootstrap.async_setup_hass(
|
||||||
config_dir=get_test_config_dir(),
|
runner.RuntimeConfig(
|
||||||
verbose=False,
|
config_dir=get_test_config_dir(),
|
||||||
log_rotate_days=10,
|
verbose=False,
|
||||||
log_file="",
|
log_rotate_days=10,
|
||||||
log_no_color=False,
|
log_file="",
|
||||||
skip_pip=True,
|
log_no_color=False,
|
||||||
safe_mode=False,
|
skip_pip=True,
|
||||||
|
safe_mode=False,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
is None
|
is None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_setup_hass_safe_mode(
|
async def test_setup_hass_safe_mode(
|
||||||
hass,
|
|
||||||
mock_enable_logging,
|
mock_enable_logging,
|
||||||
mock_is_virtual_env,
|
mock_is_virtual_env,
|
||||||
mock_mount_local_lib_path,
|
mock_mount_local_lib_path,
|
||||||
mock_ensure_config_exists,
|
mock_ensure_config_exists,
|
||||||
mock_process_ha_config_upgrade,
|
mock_process_ha_config_upgrade,
|
||||||
|
loop,
|
||||||
):
|
):
|
||||||
"""Test it works."""
|
"""Test it works."""
|
||||||
# Add a config entry to storage.
|
|
||||||
MockConfigEntry(domain="browser").add_to_hass(hass)
|
|
||||||
hass.config_entries._async_schedule_save()
|
|
||||||
await flush_store(hass.config_entries._store)
|
|
||||||
|
|
||||||
with patch("homeassistant.components.browser.setup") as browser_setup, patch(
|
with patch("homeassistant.components.browser.setup") as browser_setup, patch(
|
||||||
"homeassistant.components.http.start_http_server_and_save_config"
|
"homeassistant.components.http.start_http_server_and_save_config"
|
||||||
|
), patch(
|
||||||
|
"homeassistant.config_entries.ConfigEntries.async_domains",
|
||||||
|
return_value=["browser"],
|
||||||
):
|
):
|
||||||
hass = await bootstrap.async_setup_hass(
|
hass = await bootstrap.async_setup_hass(
|
||||||
config_dir=get_test_config_dir(),
|
runner.RuntimeConfig(
|
||||||
verbose=False,
|
config_dir=get_test_config_dir(),
|
||||||
log_rotate_days=10,
|
verbose=False,
|
||||||
log_file="",
|
log_rotate_days=10,
|
||||||
log_no_color=False,
|
log_file="",
|
||||||
skip_pip=True,
|
log_no_color=False,
|
||||||
safe_mode=True,
|
skip_pip=True,
|
||||||
|
safe_mode=True,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "safe_mode" in hass.config.components
|
assert "safe_mode" in hass.config.components
|
||||||
|
@ -522,6 +532,7 @@ async def test_setup_hass_invalid_core_config(
|
||||||
mock_mount_local_lib_path,
|
mock_mount_local_lib_path,
|
||||||
mock_ensure_config_exists,
|
mock_ensure_config_exists,
|
||||||
mock_process_ha_config_upgrade,
|
mock_process_ha_config_upgrade,
|
||||||
|
loop,
|
||||||
):
|
):
|
||||||
"""Test it works."""
|
"""Test it works."""
|
||||||
with patch(
|
with patch(
|
||||||
|
@ -529,13 +540,15 @@ async def test_setup_hass_invalid_core_config(
|
||||||
return_value={"homeassistant": {"non-existing": 1}},
|
return_value={"homeassistant": {"non-existing": 1}},
|
||||||
), patch("homeassistant.components.http.start_http_server_and_save_config"):
|
), patch("homeassistant.components.http.start_http_server_and_save_config"):
|
||||||
hass = await bootstrap.async_setup_hass(
|
hass = await bootstrap.async_setup_hass(
|
||||||
config_dir=get_test_config_dir(),
|
runner.RuntimeConfig(
|
||||||
verbose=False,
|
config_dir=get_test_config_dir(),
|
||||||
log_rotate_days=10,
|
verbose=False,
|
||||||
log_file="",
|
log_rotate_days=10,
|
||||||
log_no_color=False,
|
log_file="",
|
||||||
skip_pip=True,
|
log_no_color=False,
|
||||||
safe_mode=False,
|
skip_pip=True,
|
||||||
|
safe_mode=False,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "safe_mode" in hass.config.components
|
assert "safe_mode" in hass.config.components
|
||||||
|
@ -547,6 +560,7 @@ async def test_setup_safe_mode_if_no_frontend(
|
||||||
mock_mount_local_lib_path,
|
mock_mount_local_lib_path,
|
||||||
mock_ensure_config_exists,
|
mock_ensure_config_exists,
|
||||||
mock_process_ha_config_upgrade,
|
mock_process_ha_config_upgrade,
|
||||||
|
loop,
|
||||||
):
|
):
|
||||||
"""Test we setup safe mode if frontend didn't load."""
|
"""Test we setup safe mode if frontend didn't load."""
|
||||||
verbose = Mock()
|
verbose = Mock()
|
||||||
|
@ -566,13 +580,15 @@ async def test_setup_safe_mode_if_no_frontend(
|
||||||
},
|
},
|
||||||
), patch("homeassistant.components.http.start_http_server_and_save_config"):
|
), patch("homeassistant.components.http.start_http_server_and_save_config"):
|
||||||
hass = await bootstrap.async_setup_hass(
|
hass = await bootstrap.async_setup_hass(
|
||||||
config_dir=get_test_config_dir(),
|
runner.RuntimeConfig(
|
||||||
verbose=verbose,
|
config_dir=get_test_config_dir(),
|
||||||
log_rotate_days=log_rotate_days,
|
verbose=verbose,
|
||||||
log_file=log_file,
|
log_rotate_days=log_rotate_days,
|
||||||
log_no_color=log_no_color,
|
log_file=log_file,
|
||||||
skip_pip=True,
|
log_no_color=log_no_color,
|
||||||
safe_mode=False,
|
skip_pip=True,
|
||||||
|
safe_mode=False,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert "safe_mode" in hass.config.components
|
assert "safe_mode" in hass.config.components
|
||||||
|
|
|
@ -1104,14 +1104,13 @@ def test_timer_out_of_sync(mock_monotonic, loop):
|
||||||
assert abs(target - 14.2) < 0.001
|
assert abs(target - 14.2) < 0.001
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def test_hass_start_starts_the_timer(loop):
|
||||||
def test_hass_start_starts_the_timer(loop):
|
|
||||||
"""Test when hass starts, it starts the timer."""
|
"""Test when hass starts, it starts the timer."""
|
||||||
hass = ha.HomeAssistant(loop=loop)
|
hass = ha.HomeAssistant()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with patch("homeassistant.core._async_create_timer") as mock_timer:
|
with patch("homeassistant.core._async_create_timer") as mock_timer:
|
||||||
yield from hass.async_start()
|
await hass.async_start()
|
||||||
|
|
||||||
assert hass.state == ha.CoreState.running
|
assert hass.state == ha.CoreState.running
|
||||||
assert not hass._track_task
|
assert not hass._track_task
|
||||||
|
@ -1119,21 +1118,20 @@ def test_hass_start_starts_the_timer(loop):
|
||||||
assert mock_timer.mock_calls[0][1][0] is hass
|
assert mock_timer.mock_calls[0][1][0] is hass
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
yield from hass.async_stop()
|
await hass.async_stop()
|
||||||
assert hass.state == ha.CoreState.not_running
|
assert hass.state == ha.CoreState.not_running
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def test_start_taking_too_long(loop, caplog):
|
||||||
def test_start_taking_too_long(loop, caplog):
|
|
||||||
"""Test when async_start takes too long."""
|
"""Test when async_start takes too long."""
|
||||||
hass = ha.HomeAssistant(loop=loop)
|
hass = ha.HomeAssistant()
|
||||||
caplog.set_level(logging.WARNING)
|
caplog.set_level(logging.WARNING)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.core.timeout", side_effect=asyncio.TimeoutError
|
"homeassistant.core.timeout", side_effect=asyncio.TimeoutError
|
||||||
), patch("homeassistant.core._async_create_timer") as mock_timer:
|
), patch("homeassistant.core._async_create_timer") as mock_timer:
|
||||||
yield from hass.async_start()
|
await hass.async_start()
|
||||||
|
|
||||||
assert hass.state == ha.CoreState.running
|
assert hass.state == ha.CoreState.running
|
||||||
assert len(mock_timer.mock_calls) == 1
|
assert len(mock_timer.mock_calls) == 1
|
||||||
|
@ -1141,14 +1139,13 @@ def test_start_taking_too_long(loop, caplog):
|
||||||
assert "Something is blocking Home Assistant" in caplog.text
|
assert "Something is blocking Home Assistant" in caplog.text
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
yield from hass.async_stop()
|
await hass.async_stop()
|
||||||
assert hass.state == ha.CoreState.not_running
|
assert hass.state == ha.CoreState.not_running
|
||||||
|
|
||||||
|
|
||||||
@asyncio.coroutine
|
async def test_track_task_functions(loop):
|
||||||
def test_track_task_functions(loop):
|
|
||||||
"""Test function to start/stop track task and initial state."""
|
"""Test function to start/stop track task and initial state."""
|
||||||
hass = ha.HomeAssistant(loop=loop)
|
hass = ha.HomeAssistant()
|
||||||
try:
|
try:
|
||||||
assert hass._track_task
|
assert hass._track_task
|
||||||
|
|
||||||
|
@ -1158,7 +1155,7 @@ def test_track_task_functions(loop):
|
||||||
hass.async_track_tasks()
|
hass.async_track_tasks()
|
||||||
assert hass._track_task
|
assert hass._track_task
|
||||||
finally:
|
finally:
|
||||||
yield from hass.async_stop()
|
await hass.async_stop()
|
||||||
|
|
||||||
|
|
||||||
async def test_service_executed_with_subservices(hass):
|
async def test_service_executed_with_subservices(hass):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue