Further integration load cleanups (#23104)
* Further integration load cleanups * Fix tests * Unflake MQTT vacuum command test
This commit is contained in:
parent
930f75220c
commit
d722f4d64a
14 changed files with 160 additions and 237 deletions
|
@ -290,23 +290,27 @@ class ConfigEntry:
|
|||
self._async_cancel_retry_setup = None
|
||||
|
||||
async def async_setup(
|
||||
self, hass: HomeAssistant, *, component=None, tries=0) -> None:
|
||||
self, hass: HomeAssistant, *,
|
||||
integration: Optional[loader.Integration] = None, tries=0) -> None:
|
||||
"""Set up an entry."""
|
||||
if component is None:
|
||||
component = getattr(hass.components, self.domain)
|
||||
if integration is None:
|
||||
integration = await loader.async_get_integration(hass, self.domain)
|
||||
|
||||
component = integration.get_component()
|
||||
|
||||
# Perform migration
|
||||
if component.DOMAIN == self.domain:
|
||||
if integration.domain == self.domain:
|
||||
if not await self.async_migrate(hass):
|
||||
self.state = ENTRY_STATE_MIGRATION_ERROR
|
||||
return
|
||||
|
||||
try:
|
||||
result = await component.async_setup_entry(hass, self)
|
||||
result = await component.async_setup_entry( # type: ignore
|
||||
hass, self)
|
||||
|
||||
if not isinstance(result, bool):
|
||||
_LOGGER.error('%s.async_setup_entry did not return boolean',
|
||||
component.DOMAIN)
|
||||
integration.domain)
|
||||
result = False
|
||||
except ConfigEntryNotReady:
|
||||
self.state = ENTRY_STATE_SETUP_RETRY
|
||||
|
@ -319,18 +323,19 @@ class ConfigEntry:
|
|||
async def setup_again(now):
|
||||
"""Run setup again."""
|
||||
self._async_cancel_retry_setup = None
|
||||
await self.async_setup(hass, component=component, tries=tries)
|
||||
await self.async_setup(
|
||||
hass, integration=integration, tries=tries)
|
||||
|
||||
self._async_cancel_retry_setup = \
|
||||
hass.helpers.event.async_call_later(wait_time, setup_again)
|
||||
return
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception('Error setting up entry %s for %s',
|
||||
self.title, component.DOMAIN)
|
||||
self.title, integration.domain)
|
||||
result = False
|
||||
|
||||
# Only store setup result as state if it was not forwarded.
|
||||
if self.domain != component.DOMAIN:
|
||||
if self.domain != integration.domain:
|
||||
return
|
||||
|
||||
if result:
|
||||
|
@ -338,15 +343,17 @@ class ConfigEntry:
|
|||
else:
|
||||
self.state = ENTRY_STATE_SETUP_ERROR
|
||||
|
||||
async def async_unload(self, hass, *, component=None) -> bool:
|
||||
async def async_unload(self, hass, *, integration=None) -> bool:
|
||||
"""Unload an entry.
|
||||
|
||||
Returns if unload is possible and was successful.
|
||||
"""
|
||||
if component is None:
|
||||
component = getattr(hass.components, self.domain)
|
||||
if integration is None:
|
||||
integration = await loader.async_get_integration(hass, self.domain)
|
||||
|
||||
if component.DOMAIN == self.domain:
|
||||
component = integration.get_component()
|
||||
|
||||
if integration.domain == self.domain:
|
||||
if self.state in UNRECOVERABLE_STATES:
|
||||
return False
|
||||
|
||||
|
@ -361,7 +368,7 @@ class ConfigEntry:
|
|||
supports_unload = hasattr(component, 'async_unload_entry')
|
||||
|
||||
if not supports_unload:
|
||||
if component.DOMAIN == self.domain:
|
||||
if integration.domain == self.domain:
|
||||
self.state = ENTRY_STATE_FAILED_UNLOAD
|
||||
return False
|
||||
|
||||
|
@ -371,27 +378,29 @@ class ConfigEntry:
|
|||
assert isinstance(result, bool)
|
||||
|
||||
# Only adjust state if we unloaded the component
|
||||
if result and component.DOMAIN == self.domain:
|
||||
if result and integration.domain == self.domain:
|
||||
self.state = ENTRY_STATE_NOT_LOADED
|
||||
|
||||
return result
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception('Error unloading entry %s for %s',
|
||||
self.title, component.DOMAIN)
|
||||
if component.DOMAIN == self.domain:
|
||||
self.title, integration.domain)
|
||||
if integration.domain == self.domain:
|
||||
self.state = ENTRY_STATE_FAILED_UNLOAD
|
||||
return False
|
||||
|
||||
async def async_remove(self, hass: HomeAssistant) -> None:
|
||||
"""Invoke remove callback on component."""
|
||||
component = getattr(hass.components, self.domain)
|
||||
integration = await loader.async_get_integration(hass, self.domain)
|
||||
component = integration.get_component()
|
||||
if not hasattr(component, 'async_remove_entry'):
|
||||
return
|
||||
try:
|
||||
await component.async_remove_entry(hass, self)
|
||||
await component.async_remove_entry( # type: ignore
|
||||
hass, self)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
_LOGGER.exception('Error calling entry remove callback %s for %s',
|
||||
self.title, component.DOMAIN)
|
||||
self.title, integration.domain)
|
||||
|
||||
async def async_migrate(self, hass: HomeAssistant) -> bool:
|
||||
"""Migrate an entry.
|
||||
|
@ -624,7 +633,7 @@ class ConfigEntries:
|
|||
|
||||
self._async_schedule_save()
|
||||
|
||||
async def async_forward_entry_setup(self, entry, component):
|
||||
async def async_forward_entry_setup(self, entry, domain):
|
||||
"""Forward the setup of an entry to a different component.
|
||||
|
||||
By default an entry is setup with the component it belongs to. If that
|
||||
|
@ -635,24 +644,26 @@ class ConfigEntries:
|
|||
setup of a component, because it can cause a deadlock.
|
||||
"""
|
||||
# Setup Component if not set up yet
|
||||
if component not in self.hass.config.components:
|
||||
if domain not in self.hass.config.components:
|
||||
result = await async_setup_component(
|
||||
self.hass, component, self._hass_config)
|
||||
self.hass, domain, self._hass_config)
|
||||
|
||||
if not result:
|
||||
return False
|
||||
|
||||
await entry.async_setup(
|
||||
self.hass, component=getattr(self.hass.components, component))
|
||||
integration = await loader.async_get_integration(self.hass, domain)
|
||||
|
||||
async def async_forward_entry_unload(self, entry, component):
|
||||
await entry.async_setup(self.hass, integration=integration)
|
||||
|
||||
async def async_forward_entry_unload(self, entry, domain):
|
||||
"""Forward the unloading of an entry to a different component."""
|
||||
# It was never loaded.
|
||||
if component not in self.hass.config.components:
|
||||
if domain not in self.hass.config.components:
|
||||
return True
|
||||
|
||||
return await entry.async_unload(
|
||||
self.hass, component=getattr(self.hass.components, component))
|
||||
integration = await loader.async_get_integration(self.hass, domain)
|
||||
|
||||
return await entry.async_unload(self.hass, integration=integration)
|
||||
|
||||
async def _async_finish_flow(self, flow, result):
|
||||
"""Finish a config flow and add an entry."""
|
||||
|
@ -688,10 +699,10 @@ class ConfigEntries:
|
|||
|
||||
Handler key is the domain of the component that we want to set up.
|
||||
"""
|
||||
integration = await loader.async_get_integration(
|
||||
self.hass, handler_key)
|
||||
|
||||
if integration is None:
|
||||
try:
|
||||
integration = await loader.async_get_integration(
|
||||
self.hass, handler_key)
|
||||
except loader.IntegrationNotFound:
|
||||
raise data_entry_flow.UnknownHandler
|
||||
|
||||
handler = HANDLERS.get(handler_key)
|
||||
|
|
|
@ -22,8 +22,6 @@ from typing import (
|
|||
Dict
|
||||
)
|
||||
|
||||
from homeassistant.const import PLATFORM_FORMAT
|
||||
|
||||
# Typing imports that create a circular dependency
|
||||
# pylint: disable=using-constant-test,unused-import
|
||||
if TYPE_CHECKING:
|
||||
|
@ -38,7 +36,7 @@ DEPENDENCY_BLACKLIST = {'config'}
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
DATA_KEY = 'components'
|
||||
DATA_COMPONENTS = 'components'
|
||||
DATA_INTEGRATIONS = 'integrations'
|
||||
PACKAGE_CUSTOM_COMPONENTS = 'custom_components'
|
||||
PACKAGE_BUILTIN = 'homeassistant.components'
|
||||
|
@ -117,14 +115,14 @@ class Integration:
|
|||
|
||||
def get_component(self) -> ModuleType:
|
||||
"""Return the component."""
|
||||
cache = self.hass.data.setdefault(DATA_KEY, {})
|
||||
cache = self.hass.data.setdefault(DATA_COMPONENTS, {})
|
||||
if self.domain not in cache:
|
||||
cache[self.domain] = importlib.import_module(self.pkg_path)
|
||||
return cache[self.domain] # type: ignore
|
||||
|
||||
def get_platform(self, platform_name: str) -> ModuleType:
|
||||
"""Return a platform for an integration."""
|
||||
cache = self.hass.data.setdefault(DATA_KEY, {})
|
||||
cache = self.hass.data.setdefault(DATA_COMPONENTS, {})
|
||||
full_name = "{}.{}".format(self.domain, platform_name)
|
||||
if full_name not in cache:
|
||||
cache[full_name] = importlib.import_module(
|
||||
|
@ -212,68 +210,6 @@ class CircularDependency(LoaderError):
|
|||
self.to_domain = to_domain
|
||||
|
||||
|
||||
def set_component(hass, # type: HomeAssistant
|
||||
comp_name: str, component: Optional[ModuleType]) -> None:
|
||||
"""Set a component in the cache.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
cache = hass.data.setdefault(DATA_KEY, {})
|
||||
cache[comp_name] = component
|
||||
|
||||
|
||||
def get_platform(hass, # type: HomeAssistant
|
||||
domain: str, platform_name: str) -> Optional[ModuleType]:
|
||||
"""Try to load specified platform.
|
||||
|
||||
Example invocation: get_platform(hass, 'light', 'hue')
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
# If the platform has a component, we will limit the platform loading path
|
||||
# to be the same source (custom/built-in).
|
||||
component = _load_file(hass, platform_name, LOOKUP_PATHS)
|
||||
|
||||
# Until we have moved all platforms under their component/own folder, it
|
||||
# can be that the component is None.
|
||||
if component is not None:
|
||||
base_paths = [component.__name__.rsplit('.', 1)[0]]
|
||||
else:
|
||||
base_paths = LOOKUP_PATHS
|
||||
|
||||
platform = _load_file(
|
||||
hass, PLATFORM_FORMAT.format(domain=domain, platform=platform_name),
|
||||
base_paths)
|
||||
|
||||
if platform is not None:
|
||||
return platform
|
||||
|
||||
# Legacy platform check for custom: custom_components/light/hue.py
|
||||
# Only check if the component was also in custom components.
|
||||
if component is None or base_paths[0] == PACKAGE_CUSTOM_COMPONENTS:
|
||||
platform = _load_file(
|
||||
hass,
|
||||
PLATFORM_FORMAT.format(domain=platform_name, platform=domain),
|
||||
[PACKAGE_CUSTOM_COMPONENTS]
|
||||
)
|
||||
|
||||
if platform is None:
|
||||
if component is None:
|
||||
extra = ""
|
||||
else:
|
||||
extra = " Search path was limited to path of component: {}".format(
|
||||
base_paths[0])
|
||||
_LOGGER.error("Unable to find platform %s.%s", platform_name, extra)
|
||||
return None
|
||||
|
||||
_LOGGER.error(
|
||||
"Integrations need to be in their own folder. Change %s/%s.py to "
|
||||
"%s/%s.py. This will stop working soon.",
|
||||
domain, platform_name, platform_name, domain)
|
||||
|
||||
return platform
|
||||
|
||||
|
||||
def get_component(hass, # type: HomeAssistant
|
||||
comp_or_platform: str) -> Optional[ModuleType]:
|
||||
"""Try to load specified component.
|
||||
|
@ -298,15 +234,15 @@ def _load_file(hass, # type: HomeAssistant
|
|||
Async friendly.
|
||||
"""
|
||||
try:
|
||||
return hass.data[DATA_KEY][comp_or_platform] # type: ignore
|
||||
return hass.data[DATA_COMPONENTS][comp_or_platform] # type: ignore
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
cache = hass.data.get(DATA_KEY)
|
||||
cache = hass.data.get(DATA_COMPONENTS)
|
||||
if cache is None:
|
||||
if not _async_mount_config_dir(hass):
|
||||
return None
|
||||
cache = hass.data[DATA_KEY] = {}
|
||||
cache = hass.data[DATA_COMPONENTS] = {}
|
||||
|
||||
for path in ('{}.{}'.format(base, comp_or_platform)
|
||||
for base in base_paths):
|
||||
|
@ -392,9 +328,18 @@ class Components:
|
|||
|
||||
def __getattr__(self, comp_name: str) -> ModuleWrapper:
|
||||
"""Fetch a component."""
|
||||
component = get_component(self._hass, comp_name)
|
||||
# Test integration cache
|
||||
integration = self._hass.data.get(DATA_INTEGRATIONS, {}).get(comp_name)
|
||||
|
||||
if integration:
|
||||
component = integration.get_component()
|
||||
else:
|
||||
# Fallback to importing old-school
|
||||
component = _load_file(self._hass, comp_name, LOOKUP_PATHS)
|
||||
|
||||
if component is None:
|
||||
raise ImportError('Unable to load {}'.format(comp_name))
|
||||
|
||||
wrapped = ModuleWrapper(self._hass, component)
|
||||
setattr(self, comp_name, wrapped)
|
||||
return wrapped
|
||||
|
|
|
@ -168,12 +168,11 @@ async def _async_setup_component(hass: core.HomeAssistant,
|
|||
if result is not True:
|
||||
log_error("Component {!r} did not return boolean if setup was "
|
||||
"successful. Disabling component.".format(domain))
|
||||
loader.set_component(hass, domain, None)
|
||||
return False
|
||||
|
||||
if hass.config_entries:
|
||||
for entry in hass.config_entries.async_entries(domain):
|
||||
await entry.async_setup(hass, component=component)
|
||||
await entry.async_setup(hass, integration=integration)
|
||||
|
||||
hass.config.components.add(component.DOMAIN) # type: ignore
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import functools as ft
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import uuid
|
||||
import sys
|
||||
import threading
|
||||
|
||||
|
@ -607,7 +608,7 @@ class MockConfigEntry(config_entries.ConfigEntry):
|
|||
connection_class=config_entries.CONN_CLASS_UNKNOWN):
|
||||
"""Initialize a mock config entry."""
|
||||
kwargs = {
|
||||
'entry_id': entry_id or 'mock-id',
|
||||
'entry_id': entry_id or uuid.uuid4().hex,
|
||||
'domain': domain,
|
||||
'data': data or {},
|
||||
'options': options,
|
||||
|
@ -911,7 +912,7 @@ def mock_integration(hass, module):
|
|||
hass.data.setdefault(
|
||||
loader.DATA_INTEGRATIONS, {}
|
||||
)[module.DOMAIN] = integration
|
||||
hass.data.setdefault(loader.DATA_KEY, {})[module.DOMAIN] = module
|
||||
hass.data.setdefault(loader.DATA_COMPONENTS, {})[module.DOMAIN] = module
|
||||
|
||||
|
||||
def mock_entity_platform(hass, platform_path, module):
|
||||
|
@ -921,8 +922,8 @@ def mock_entity_platform(hass, platform_path, module):
|
|||
hue.light.
|
||||
"""
|
||||
domain, platform_name = platform_path.split('.')
|
||||
integration_cache = hass.data.setdefault(loader.DATA_KEY, {})
|
||||
module_cache = hass.data.setdefault(loader.DATA_KEY, {})
|
||||
integration_cache = hass.data.setdefault(loader.DATA_COMPONENTS, {})
|
||||
module_cache = hass.data.setdefault(loader.DATA_COMPONENTS, {})
|
||||
|
||||
if platform_name not in integration_cache:
|
||||
mock_integration(hass, MockModule(platform_name))
|
||||
|
|
|
@ -12,15 +12,15 @@ from homeassistant.config_entries import HANDLERS
|
|||
from homeassistant.core import callback
|
||||
from homeassistant.setup import async_setup_component
|
||||
from homeassistant.components.config import config_entries
|
||||
from homeassistant.loader import set_component
|
||||
|
||||
from tests.common import MockConfigEntry, MockModule, mock_coro_func
|
||||
from tests.common import (
|
||||
MockConfigEntry, MockModule, mock_coro_func, mock_integration)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_test_component(hass):
|
||||
"""Ensure a component called 'test' exists."""
|
||||
set_component(hass, 'test', MockModule('test'))
|
||||
mock_integration(hass, MockModule('test'))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -244,8 +244,8 @@ def test_abort(hass, client):
|
|||
@asyncio.coroutine
|
||||
def test_create_account(hass, client):
|
||||
"""Test a flow that creates an account."""
|
||||
set_component(
|
||||
hass, 'test',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('test', async_setup_entry=mock_coro_func(True)))
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
|
@ -283,8 +283,8 @@ def test_create_account(hass, client):
|
|||
@asyncio.coroutine
|
||||
def test_two_step_flow(hass, client):
|
||||
"""Test we can finish a two step flow."""
|
||||
set_component(
|
||||
hass, 'test',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('test', async_setup_entry=mock_coro_func(True)))
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
|
@ -349,8 +349,8 @@ def test_two_step_flow(hass, client):
|
|||
|
||||
async def test_continue_flow_unauth(hass, client, hass_admin_user):
|
||||
"""Test we can't finish a two step flow."""
|
||||
set_component(
|
||||
hass, 'test',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('test', async_setup_entry=mock_coro_func(True)))
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
|
@ -562,8 +562,8 @@ async def test_options_flow(hass, client):
|
|||
|
||||
async def test_two_step_options_flow(hass, client):
|
||||
"""Test we can finish a two step options flow."""
|
||||
set_component(
|
||||
hass, 'test',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('test', async_setup_entry=mock_coro_func(True)))
|
||||
|
||||
class TestFlow(core_ce.ConfigFlow):
|
||||
|
|
|
@ -136,9 +136,10 @@ async def test_all_commands(hass, mock_publish):
|
|||
entity_id='vacuum.mqtttest')
|
||||
await hass.async_block_till_done()
|
||||
await hass.async_block_till_done()
|
||||
mock_publish.async_publish.assert_called_once_with(
|
||||
'vacuum/send_command', '{"command": "44 FE 93", "key": "value"}',
|
||||
0, False)
|
||||
assert json.loads(mock_publish.async_publish.mock_calls[-1][1][1]) == {
|
||||
"command": "44 FE 93",
|
||||
"key": "value"
|
||||
}
|
||||
|
||||
|
||||
async def test_status(hass, mock_publish):
|
||||
|
|
|
@ -7,7 +7,7 @@ from homeassistant import core, loader
|
|||
from homeassistant.components import switch
|
||||
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
from tests.common import get_test_home_assistant, mock_entity_platform
|
||||
from tests.components.switch import common
|
||||
|
||||
|
||||
|
@ -80,7 +80,7 @@ class TestSwitch(unittest.TestCase):
|
|||
test_platform = loader.get_component(self.hass, 'test.switch')
|
||||
test_platform.init(True)
|
||||
|
||||
loader.set_component(self.hass, 'switch.test2', test_platform)
|
||||
mock_entity_platform(self.hass, 'switch.test2', test_platform)
|
||||
test_platform.init(False)
|
||||
|
||||
assert setup_component(
|
||||
|
|
|
@ -3,9 +3,10 @@ from unittest.mock import patch, Mock
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries, data_entry_flow, loader, setup
|
||||
from homeassistant import config_entries, data_entry_flow, setup
|
||||
from homeassistant.helpers import config_entry_flow
|
||||
from tests.common import MockConfigEntry, MockModule, mock_coro
|
||||
from tests.common import (
|
||||
MockConfigEntry, MockModule, mock_coro, mock_integration)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -101,7 +102,7 @@ async def test_discovery_confirmation(hass, discovery_flow_conf):
|
|||
|
||||
async def test_multiple_discoveries(hass, discovery_flow_conf):
|
||||
"""Test we only create one instance for multiple discoveries."""
|
||||
loader.set_component(hass, 'test', MockModule('test'))
|
||||
mock_integration(hass, MockModule('test'))
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
'test', context={'source': config_entries.SOURCE_DISCOVERY}, data={})
|
||||
|
@ -115,7 +116,7 @@ async def test_multiple_discoveries(hass, discovery_flow_conf):
|
|||
|
||||
async def test_only_one_in_progress(hass, discovery_flow_conf):
|
||||
"""Test a user initialized one will finish and cancel discovered one."""
|
||||
loader.set_component(hass, 'test', MockModule('test'))
|
||||
mock_integration(hass, MockModule('test'))
|
||||
|
||||
# Discovery starts flow
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
@ -202,7 +203,7 @@ async def test_webhook_create_cloudhook(hass, webhook_flow_conf):
|
|||
async_setup_entry = Mock(return_value=mock_coro(True))
|
||||
async_unload_entry = Mock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(hass, 'test_single', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'test_single',
|
||||
async_setup_entry=async_setup_entry,
|
||||
async_unload_entry=async_unload_entry,
|
||||
|
|
|
@ -3,13 +3,14 @@ from unittest.mock import patch
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant import loader, setup
|
||||
from homeassistant import setup
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import discovery
|
||||
|
||||
from tests.common import (
|
||||
get_test_home_assistant, MockModule, MockPlatform, mock_coro)
|
||||
get_test_home_assistant, MockModule, MockPlatform, mock_coro,
|
||||
mock_integration, mock_entity_platform)
|
||||
|
||||
|
||||
class TestHelpersDiscovery:
|
||||
|
@ -128,17 +129,17 @@ class TestHelpersDiscovery:
|
|||
"""Set up mock platform."""
|
||||
platform_calls.append('disc' if discovery_info else 'component')
|
||||
|
||||
loader.set_component(
|
||||
self.hass, 'test_component',
|
||||
mock_integration(
|
||||
self.hass,
|
||||
MockModule('test_component', setup=component_setup))
|
||||
|
||||
# dependencies are only set in component level
|
||||
# since we are using manifest to hold them
|
||||
loader.set_component(
|
||||
self.hass, 'test_circular',
|
||||
mock_integration(
|
||||
self.hass,
|
||||
MockModule('test_circular', dependencies=['test_component']))
|
||||
loader.set_component(
|
||||
self.hass, 'test_circular.switch',
|
||||
mock_entity_platform(
|
||||
self.hass, 'switch.test_circular',
|
||||
MockPlatform(setup_platform))
|
||||
|
||||
setup.setup_component(self.hass, 'test_component', {
|
||||
|
@ -180,12 +181,12 @@ class TestHelpersDiscovery:
|
|||
component_calls.append(1)
|
||||
return True
|
||||
|
||||
loader.set_component(
|
||||
self.hass, 'test_component1',
|
||||
mock_integration(
|
||||
self.hass,
|
||||
MockModule('test_component1', setup=component1_setup))
|
||||
|
||||
loader.set_component(
|
||||
self.hass, 'test_component2',
|
||||
mock_integration(
|
||||
self.hass,
|
||||
MockModule('test_component2', setup=component2_setup))
|
||||
|
||||
@callback
|
||||
|
|
|
@ -10,7 +10,6 @@ from datetime import timedelta
|
|||
import pytest
|
||||
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.loader as loader
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.components import group
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
|
@ -226,9 +225,8 @@ def test_platform_not_ready(hass):
|
|||
"""Test that we retry when platform not ready."""
|
||||
platform1_setup = Mock(side_effect=[PlatformNotReady, PlatformNotReady,
|
||||
None])
|
||||
loader.set_component(hass, 'mod1',
|
||||
MockModule('mod1'))
|
||||
loader.set_component(hass, 'mod1.test_domain',
|
||||
mock_integration(hass, MockModule('mod1'))
|
||||
mock_entity_platform(hass, 'test_domain.mod1',
|
||||
MockPlatform(platform1_setup))
|
||||
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||
|
@ -326,12 +324,10 @@ def test_setup_dependencies_platform(hass):
|
|||
We're explictely testing that we process dependencies even if a component
|
||||
with the same name has already been loaded.
|
||||
"""
|
||||
loader.set_component(hass, 'test_component',
|
||||
MockModule('test_component',
|
||||
dependencies=['test_component2']))
|
||||
loader.set_component(hass, 'test_component2',
|
||||
MockModule('test_component2'))
|
||||
loader.set_component(hass, 'test_component.test_domain', MockPlatform())
|
||||
mock_integration(hass, MockModule('test_component',
|
||||
dependencies=['test_component2']))
|
||||
mock_integration(hass, MockModule('test_component2'))
|
||||
mock_entity_platform(hass, 'test_domain.test_component', MockPlatform())
|
||||
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ from unittest.mock import MagicMock, patch
|
|||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries, loader, data_entry_flow
|
||||
from homeassistant import config_entries, data_entry_flow
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
@ -49,8 +49,8 @@ async def test_call_setup_entry(hass):
|
|||
mock_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
mock_migrate_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(
|
||||
hass, 'comp',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('comp', async_setup_entry=mock_setup_entry,
|
||||
async_migrate_entry=mock_migrate_entry))
|
||||
|
||||
|
@ -70,8 +70,8 @@ async def test_call_async_migrate_entry(hass):
|
|||
mock_migrate_entry = MagicMock(return_value=mock_coro(True))
|
||||
mock_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(
|
||||
hass, 'comp',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('comp', async_setup_entry=mock_setup_entry,
|
||||
async_migrate_entry=mock_migrate_entry))
|
||||
|
||||
|
@ -91,8 +91,8 @@ async def test_call_async_migrate_entry_failure_false(hass):
|
|||
mock_migrate_entry = MagicMock(return_value=mock_coro(False))
|
||||
mock_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(
|
||||
hass, 'comp',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('comp', async_setup_entry=mock_setup_entry,
|
||||
async_migrate_entry=mock_migrate_entry))
|
||||
|
||||
|
@ -113,8 +113,8 @@ async def test_call_async_migrate_entry_failure_exception(hass):
|
|||
return_value=mock_coro(exception=Exception))
|
||||
mock_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(
|
||||
hass, 'comp',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('comp', async_setup_entry=mock_setup_entry,
|
||||
async_migrate_entry=mock_migrate_entry))
|
||||
|
||||
|
@ -135,8 +135,8 @@ async def test_call_async_migrate_entry_failure_not_bool(hass):
|
|||
return_value=mock_coro())
|
||||
mock_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(
|
||||
hass, 'comp',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('comp', async_setup_entry=mock_setup_entry,
|
||||
async_migrate_entry=mock_migrate_entry))
|
||||
|
||||
|
@ -155,8 +155,8 @@ async def test_call_async_migrate_entry_failure_not_supported(hass):
|
|||
|
||||
mock_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(
|
||||
hass, 'comp',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('comp', async_setup_entry=mock_setup_entry))
|
||||
|
||||
result = await async_setup_component(hass, 'comp', {})
|
||||
|
@ -265,7 +265,7 @@ async def test_remove_entry_handles_callback_error(hass, manager):
|
|||
mock_unload_entry = MagicMock(return_value=mock_coro(True))
|
||||
mock_remove_entry = MagicMock(
|
||||
side_effect=lambda *args, **kwargs: mock_coro())
|
||||
loader.set_component(hass, 'test', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'test',
|
||||
async_setup_entry=mock_setup_entry,
|
||||
async_unload_entry=mock_unload_entry,
|
||||
|
@ -304,13 +304,12 @@ def test_remove_entry_raises(hass, manager):
|
|||
"""Mock unload entry function."""
|
||||
raise Exception("BROKEN")
|
||||
|
||||
loader.set_component(
|
||||
hass, 'test',
|
||||
MockModule('comp', async_unload_entry=mock_unload_entry))
|
||||
mock_integration(hass, MockModule(
|
||||
'comp', async_unload_entry=mock_unload_entry))
|
||||
|
||||
MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
|
||||
MockConfigEntry(
|
||||
domain='test',
|
||||
domain='comp',
|
||||
entry_id='test2',
|
||||
state=config_entries.ENTRY_STATE_LOADED
|
||||
).add_to_manager(manager)
|
||||
|
@ -330,15 +329,14 @@ def test_remove_entry_raises(hass, manager):
|
|||
|
||||
@asyncio.coroutine
|
||||
def test_remove_entry_if_not_loaded(hass, manager):
|
||||
"""Test that we can remove an entry."""
|
||||
"""Test that we can remove an entry that is not loaded."""
|
||||
mock_unload_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(
|
||||
hass, 'test',
|
||||
MockModule('comp', async_unload_entry=mock_unload_entry))
|
||||
mock_integration(hass, MockModule(
|
||||
'comp', async_unload_entry=mock_unload_entry))
|
||||
|
||||
MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
|
||||
MockConfigEntry(domain='test', entry_id='test2').add_to_manager(manager)
|
||||
MockConfigEntry(domain='comp', entry_id='test2').add_to_manager(manager)
|
||||
MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager)
|
||||
|
||||
assert [item.entry_id for item in manager.async_entries()] == \
|
||||
|
@ -352,7 +350,7 @@ def test_remove_entry_if_not_loaded(hass, manager):
|
|||
assert [item.entry_id for item in manager.async_entries()] == \
|
||||
['test1', 'test3']
|
||||
|
||||
assert len(mock_unload_entry.mock_calls) == 1
|
||||
assert len(mock_unload_entry.mock_calls) == 0
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
|
@ -360,8 +358,8 @@ def test_add_entry_calls_setup_entry(hass, manager):
|
|||
"""Test we call setup_config_entry."""
|
||||
mock_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(
|
||||
hass, 'comp',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('comp', async_setup_entry=mock_setup_entry))
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
|
@ -416,9 +414,8 @@ def test_domains_gets_uniques(manager):
|
|||
|
||||
async def test_saving_and_loading(hass):
|
||||
"""Test that we're saving and loading correctly."""
|
||||
loader.set_component(
|
||||
hass, 'test',
|
||||
MockModule('test', async_setup_entry=lambda *args: mock_coro(True)))
|
||||
mock_integration(hass, MockModule(
|
||||
'test', async_setup_entry=lambda *args: mock_coro(True)))
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
VERSION = 5
|
||||
|
@ -480,13 +477,13 @@ async def test_forward_entry_sets_up_component(hass):
|
|||
entry = MockConfigEntry(domain='original')
|
||||
|
||||
mock_original_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
loader.set_component(
|
||||
hass, 'original',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('original', async_setup_entry=mock_original_setup_entry))
|
||||
|
||||
mock_forwarded_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
loader.set_component(
|
||||
hass, 'forwarded',
|
||||
mock_integration(
|
||||
hass,
|
||||
MockModule('forwarded', async_setup_entry=mock_forwarded_setup_entry))
|
||||
|
||||
await hass.config_entries.async_forward_entry_setup(entry, 'forwarded')
|
||||
|
@ -500,7 +497,7 @@ async def test_forward_entry_does_not_setup_entry_if_setup_fails(hass):
|
|||
|
||||
mock_setup = MagicMock(return_value=mock_coro(False))
|
||||
mock_setup_entry = MagicMock()
|
||||
hass, loader.set_component(hass, 'forwarded', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'forwarded',
|
||||
async_setup=mock_setup,
|
||||
async_setup_entry=mock_setup_entry,
|
||||
|
@ -513,7 +510,7 @@ async def test_forward_entry_does_not_setup_entry_if_setup_fails(hass):
|
|||
|
||||
async def test_discovery_notification(hass):
|
||||
"""Test that we create/dismiss a notification when source is discovery."""
|
||||
loader.set_component(hass, 'test', MockModule('test'))
|
||||
mock_integration(hass, MockModule('test'))
|
||||
await async_setup_component(hass, 'persistent_notification', {})
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
|
@ -550,7 +547,7 @@ async def test_discovery_notification(hass):
|
|||
|
||||
async def test_discovery_notification_not_created(hass):
|
||||
"""Test that we not create a notification when discovery is aborted."""
|
||||
loader.set_component(hass, 'test', MockModule('test'))
|
||||
mock_integration(hass, MockModule('test'))
|
||||
await async_setup_component(hass, 'persistent_notification', {})
|
||||
|
||||
class TestFlow(config_entries.ConfigFlow):
|
||||
|
@ -630,8 +627,8 @@ async def test_setup_raise_not_ready(hass, caplog):
|
|||
entry = MockConfigEntry(domain='test')
|
||||
|
||||
mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady)
|
||||
loader.set_component(
|
||||
hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry))
|
||||
mock_integration(
|
||||
hass, MockModule('test', async_setup_entry=mock_setup_entry))
|
||||
|
||||
with patch('homeassistant.helpers.event.async_call_later') as mock_call:
|
||||
await entry.async_setup(hass)
|
||||
|
@ -656,8 +653,8 @@ async def test_setup_retrying_during_unload(hass):
|
|||
entry = MockConfigEntry(domain='test')
|
||||
|
||||
mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady)
|
||||
loader.set_component(
|
||||
hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry))
|
||||
mock_integration(
|
||||
hass, MockModule('test', async_setup_entry=mock_setup_entry))
|
||||
|
||||
with patch('homeassistant.helpers.event.async_call_later') as mock_call:
|
||||
await entry.async_setup(hass)
|
||||
|
@ -718,7 +715,7 @@ async def test_entry_setup_succeed(hass, manager):
|
|||
mock_setup = MagicMock(return_value=mock_coro(True))
|
||||
mock_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(hass, 'comp', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'comp',
|
||||
async_setup=mock_setup,
|
||||
async_setup_entry=mock_setup_entry
|
||||
|
@ -748,7 +745,7 @@ async def test_entry_setup_invalid_state(hass, manager, state):
|
|||
mock_setup = MagicMock(return_value=mock_coro(True))
|
||||
mock_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(hass, 'comp', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'comp',
|
||||
async_setup=mock_setup,
|
||||
async_setup_entry=mock_setup_entry
|
||||
|
@ -772,7 +769,7 @@ async def test_entry_unload_succeed(hass, manager):
|
|||
|
||||
async_unload_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(hass, 'comp', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'comp',
|
||||
async_unload_entry=async_unload_entry
|
||||
))
|
||||
|
@ -797,7 +794,7 @@ async def test_entry_unload_failed_to_load(hass, manager, state):
|
|||
|
||||
async_unload_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(hass, 'comp', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'comp',
|
||||
async_unload_entry=async_unload_entry
|
||||
))
|
||||
|
@ -821,7 +818,7 @@ async def test_entry_unload_invalid_state(hass, manager, state):
|
|||
|
||||
async_unload_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(hass, 'comp', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'comp',
|
||||
async_unload_entry=async_unload_entry
|
||||
))
|
||||
|
@ -845,7 +842,7 @@ async def test_entry_reload_succeed(hass, manager):
|
|||
async_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
async_unload_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(hass, 'comp', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'comp',
|
||||
async_setup=async_setup,
|
||||
async_setup_entry=async_setup_entry,
|
||||
|
@ -876,7 +873,7 @@ async def test_entry_reload_not_loaded(hass, manager, state):
|
|||
async_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
async_unload_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(hass, 'comp', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'comp',
|
||||
async_setup=async_setup,
|
||||
async_setup_entry=async_setup_entry,
|
||||
|
@ -906,7 +903,7 @@ async def test_entry_reload_error(hass, manager, state):
|
|||
async_setup_entry = MagicMock(return_value=mock_coro(True))
|
||||
async_unload_entry = MagicMock(return_value=mock_coro(True))
|
||||
|
||||
loader.set_component(hass, 'comp', MockModule(
|
||||
mock_integration(hass, MockModule(
|
||||
'comp',
|
||||
async_setup=async_setup,
|
||||
async_setup_entry=async_setup_entry,
|
||||
|
|
|
@ -8,14 +8,6 @@ from homeassistant.components.hue import light as hue_light
|
|||
from tests.common import MockModule, async_mock_service, mock_integration
|
||||
|
||||
|
||||
def test_set_component(hass):
|
||||
"""Test if set_component works."""
|
||||
comp = object()
|
||||
loader.set_component(hass, 'switch.test_set', comp)
|
||||
|
||||
assert loader.get_component(hass, 'switch.test_set') is comp
|
||||
|
||||
|
||||
def test_get_component(hass):
|
||||
"""Test if get_component works."""
|
||||
assert http == loader.get_component(hass, 'http')
|
||||
|
@ -119,27 +111,6 @@ async def test_log_warning_custom_component(hass, caplog):
|
|||
assert 'You are using a custom component for test.light' in caplog.text
|
||||
|
||||
|
||||
async def test_get_platform(hass, caplog):
|
||||
"""Test get_platform."""
|
||||
# Test we prefer embedded over normal platforms."""
|
||||
embedded_platform = loader.get_platform(hass, 'switch', 'test_embedded')
|
||||
assert embedded_platform.__name__ == \
|
||||
'custom_components.test_embedded.switch'
|
||||
|
||||
caplog.clear()
|
||||
|
||||
legacy_platform = loader.get_platform(hass, 'switch', 'test_legacy')
|
||||
assert legacy_platform.__name__ == 'custom_components.switch.test_legacy'
|
||||
assert 'Integrations need to be in their own folder.' in caplog.text
|
||||
|
||||
|
||||
async def test_get_platform_enforces_component_path(hass, caplog):
|
||||
"""Test that existence of a component limits lookup path of platforms."""
|
||||
assert loader.get_platform(hass, 'comp_path_test', 'hue') is None
|
||||
assert ('Search path was limited to path of component: '
|
||||
'homeassistant.components') in caplog.text
|
||||
|
||||
|
||||
async def test_get_integration(hass):
|
||||
"""Test resolving integration."""
|
||||
integration = await loader.async_get_integration(hass, 'hue')
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
import os
|
||||
from unittest.mock import patch, call
|
||||
|
||||
from homeassistant import loader, setup
|
||||
from homeassistant import setup
|
||||
from homeassistant.requirements import (
|
||||
CONSTRAINT_FILE, PackageLoadable, async_process_requirements)
|
||||
|
||||
import pkg_resources
|
||||
|
||||
from tests.common import get_test_home_assistant, MockModule, mock_coro
|
||||
from tests.common import (
|
||||
get_test_home_assistant, MockModule, mock_coro, mock_integration)
|
||||
|
||||
RESOURCE_DIR = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), '..', 'resources'))
|
||||
|
@ -43,8 +44,8 @@ class TestRequirements:
|
|||
mock_venv.return_value = True
|
||||
mock_dirname.return_value = 'ha_package_path'
|
||||
self.hass.config.skip_pip = False
|
||||
loader.set_component(
|
||||
self.hass, 'comp',
|
||||
mock_integration(
|
||||
self.hass,
|
||||
MockModule('comp', requirements=['package==0.0.1']))
|
||||
assert setup.setup_component(self.hass, 'comp', {})
|
||||
assert 'comp' in self.hass.config.components
|
||||
|
@ -60,8 +61,8 @@ class TestRequirements:
|
|||
"""Test requirement installed in deps directory."""
|
||||
mock_dirname.return_value = 'ha_package_path'
|
||||
self.hass.config.skip_pip = False
|
||||
loader.set_component(
|
||||
self.hass, 'comp',
|
||||
mock_integration(
|
||||
self.hass,
|
||||
MockModule('comp', requirements=['package==0.0.1']))
|
||||
assert setup.setup_component(self.hass, 'comp', {})
|
||||
assert 'comp' in self.hass.config.components
|
||||
|
|
|
@ -494,7 +494,6 @@ class TestSetup:
|
|||
MockModule('disabled_component', setup=lambda hass, config: None))
|
||||
|
||||
assert not setup.setup_component(self.hass, 'disabled_component', {})
|
||||
assert loader.get_component(self.hass, 'disabled_component') is None
|
||||
assert 'disabled_component' not in self.hass.config.components
|
||||
|
||||
self.hass.data.pop(setup.DATA_SETUP)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue