Fix automations listening to HOMEASSISTANT_START (#6936)
* Fire EVENT_HOMEASSISTANT_START automations off right away while starting * Actually have core state be set to 'starting' during boot * Fix correct start implementation * Test and deprecate event automation platform on start * Fix doc strings * Remove shutting down exception * More strict when to mark an instance as finished * Add automation platform to listen for start/shutdown * When we stop we should wait till it's all done * Fix testing * Fix async bugs in tests * Only set UVLOOP when hass starts from CLI * This hangs normal asyncio event loop * Clean up Z-Wave node entity test
This commit is contained in:
parent
289d6b6605
commit
29f385ea76
23 changed files with 258 additions and 97 deletions
|
@ -20,6 +20,17 @@ from homeassistant.const import (
|
||||||
from homeassistant.util.async import run_callback_threadsafe
|
from homeassistant.util.async import run_callback_threadsafe
|
||||||
|
|
||||||
|
|
||||||
|
def attempt_use_uvloop():
|
||||||
|
"""Attempt to use uvloop."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
try:
|
||||||
|
import uvloop
|
||||||
|
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
def monkey_patch_asyncio():
|
def monkey_patch_asyncio():
|
||||||
"""Replace weakref.WeakSet to address Python 3 bug.
|
"""Replace weakref.WeakSet to address Python 3 bug.
|
||||||
|
|
||||||
|
@ -311,8 +322,7 @@ def setup_and_run_hass(config_dir: str,
|
||||||
EVENT_HOMEASSISTANT_START, open_browser
|
EVENT_HOMEASSISTANT_START, open_browser
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.start()
|
return hass.start()
|
||||||
return hass.exit_code
|
|
||||||
|
|
||||||
|
|
||||||
def try_to_restart() -> None:
|
def try_to_restart() -> None:
|
||||||
|
@ -359,11 +369,13 @@ def try_to_restart() -> None:
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
"""Start Home Assistant."""
|
"""Start Home Assistant."""
|
||||||
|
validate_python()
|
||||||
|
|
||||||
|
attempt_use_uvloop()
|
||||||
|
|
||||||
if sys.version_info[:3] < (3, 5, 3):
|
if sys.version_info[:3] < (3, 5, 3):
|
||||||
monkey_patch_asyncio()
|
monkey_patch_asyncio()
|
||||||
|
|
||||||
validate_python()
|
|
||||||
|
|
||||||
args = get_arguments()
|
args = get_arguments()
|
||||||
|
|
||||||
if args.script is not None:
|
if args.script is not None:
|
||||||
|
|
|
@ -74,8 +74,6 @@ def async_from_config_dict(config: Dict[str, Any],
|
||||||
This method is a coroutine.
|
This method is a coroutine.
|
||||||
"""
|
"""
|
||||||
start = time()
|
start = time()
|
||||||
hass.async_track_tasks()
|
|
||||||
|
|
||||||
core_config = config.get(core.DOMAIN, {})
|
core_config = config.get(core.DOMAIN, {})
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -140,10 +138,10 @@ def async_from_config_dict(config: Dict[str, Any],
|
||||||
continue
|
continue
|
||||||
hass.async_add_job(async_setup_component(hass, component, config))
|
hass.async_add_job(async_setup_component(hass, component, config))
|
||||||
|
|
||||||
yield from hass.async_stop_track_tasks()
|
yield from hass.async_block_till_done()
|
||||||
|
|
||||||
stop = time()
|
stop = time()
|
||||||
_LOGGER.info('Home Assistant initialized in %ss', round(stop-start, 2))
|
_LOGGER.info('Home Assistant initialized in %.2fs', stop-start)
|
||||||
|
|
||||||
async_register_signal_handling(hass)
|
async_register_signal_handling(hass)
|
||||||
return hass
|
return hass
|
||||||
|
|
|
@ -2,15 +2,15 @@
|
||||||
Offer event listening automation rules.
|
Offer event listening automation rules.
|
||||||
|
|
||||||
For more details about this automation rule, please refer to the documentation
|
For more details about this automation rule, please refer to the documentation
|
||||||
at https://home-assistant.io/components/automation/#event-trigger
|
at https://home-assistant.io/docs/automation/trigger/#event-trigger
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import callback, CoreState
|
||||||
from homeassistant.const import CONF_PLATFORM
|
from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_START
|
||||||
from homeassistant.helpers import config_validation as cv
|
from homeassistant.helpers import config_validation as cv
|
||||||
|
|
||||||
CONF_EVENT_TYPE = "event_type"
|
CONF_EVENT_TYPE = "event_type"
|
||||||
|
@ -31,6 +31,19 @@ def async_trigger(hass, config, action):
|
||||||
event_type = config.get(CONF_EVENT_TYPE)
|
event_type = config.get(CONF_EVENT_TYPE)
|
||||||
event_data = config.get(CONF_EVENT_DATA)
|
event_data = config.get(CONF_EVENT_DATA)
|
||||||
|
|
||||||
|
if (event_type == EVENT_HOMEASSISTANT_START and
|
||||||
|
hass.state == CoreState.starting):
|
||||||
|
_LOGGER.warning('Deprecation: Automations should not listen to event '
|
||||||
|
"'homeassistant_start'. Use platform 'homeassistant' "
|
||||||
|
'instead. Feature will be removed in 0.45')
|
||||||
|
hass.async_run_job(action, {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event': None,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return lambda: None
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def handle_event(event):
|
def handle_event(event):
|
||||||
"""Listen for events and calls the action when data matches."""
|
"""Listen for events and calls the action when data matches."""
|
||||||
|
|
55
homeassistant/components/automation/homeassistant.py
Normal file
55
homeassistant/components/automation/homeassistant.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
"""
|
||||||
|
Offer Home Assistant core automation rules.
|
||||||
|
|
||||||
|
For more details about this automation rule, please refer to the documentation
|
||||||
|
at https://home-assistant.io/components/automation/#homeassistant-trigger
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.core import callback, CoreState
|
||||||
|
from homeassistant.const import (
|
||||||
|
CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP)
|
||||||
|
|
||||||
|
EVENT_START = 'start'
|
||||||
|
EVENT_SHUTDOWN = 'shutdown'
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
TRIGGER_SCHEMA = vol.Schema({
|
||||||
|
vol.Required(CONF_PLATFORM): 'homeassistant',
|
||||||
|
vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN),
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def async_trigger(hass, config, action):
|
||||||
|
"""Listen for events based on configuration."""
|
||||||
|
event = config.get(CONF_EVENT)
|
||||||
|
|
||||||
|
if event == EVENT_SHUTDOWN:
|
||||||
|
@callback
|
||||||
|
def hass_shutdown(event):
|
||||||
|
"""Called when Home Assistant is shutting down."""
|
||||||
|
hass.async_run_job(action, {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'homeassistant',
|
||||||
|
'event': event,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP,
|
||||||
|
hass_shutdown)
|
||||||
|
|
||||||
|
# Automation are enabled while hass is starting up, fire right away
|
||||||
|
# Check state because a config reload shouldn't trigger it.
|
||||||
|
elif hass.state == CoreState.starting:
|
||||||
|
hass.async_run_job(action, {
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'homeassistant',
|
||||||
|
'event': event,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return lambda: None
|
|
@ -70,7 +70,7 @@ def async_trigger(hass, config, action):
|
||||||
nonlocal held_less_than, held_more_than
|
nonlocal held_less_than, held_more_than
|
||||||
pressed_time = dt_util.utcnow()
|
pressed_time = dt_util.utcnow()
|
||||||
if held_more_than is None and held_less_than is None:
|
if held_more_than is None and held_less_than is None:
|
||||||
call_action()
|
hass.add_job(call_action)
|
||||||
if held_more_than is not None and held_less_than is None:
|
if held_more_than is not None and held_less_than is None:
|
||||||
cancel_pressed_more_than = track_point_in_utc_time(
|
cancel_pressed_more_than = track_point_in_utc_time(
|
||||||
hass,
|
hass,
|
||||||
|
@ -88,7 +88,7 @@ def async_trigger(hass, config, action):
|
||||||
held_time = dt_util.utcnow() - pressed_time
|
held_time = dt_util.utcnow() - pressed_time
|
||||||
if held_less_than is not None and held_time < held_less_than:
|
if held_less_than is not None and held_time < held_less_than:
|
||||||
if held_more_than is None or held_time > held_more_than:
|
if held_more_than is None or held_time > held_more_than:
|
||||||
call_action()
|
hass.add_job(call_action)
|
||||||
|
|
||||||
hass.data['litejet_system'].on_switch_pressed(number, pressed)
|
hass.data['litejet_system'].on_switch_pressed(number, pressed)
|
||||||
hass.data['litejet_system'].on_switch_released(number, released)
|
hass.data['litejet_system'].on_switch_released(number, released)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Offer MQTT listening automation rules.
|
Offer MQTT listening automation rules.
|
||||||
|
|
||||||
For more details about this automation rule, please refer to the documentation
|
For more details about this automation rule, please refer to the documentation
|
||||||
at https://home-assistant.io/components/automation/#mqtt-trigger
|
at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Offer numeric state listening automation rules.
|
Offer numeric state listening automation rules.
|
||||||
|
|
||||||
For more details about this automation rule, please refer to the documentation
|
For more details about this automation rule, please refer to the documentation
|
||||||
at https://home-assistant.io/components/automation/#numeric-state-trigger
|
at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Offer state listening automation rules.
|
Offer state listening automation rules.
|
||||||
|
|
||||||
For more details about this automation rule, please refer to the documentation
|
For more details about this automation rule, please refer to the documentation
|
||||||
at https://home-assistant.io/components/automation/#state-trigger
|
at https://home-assistant.io/docs/automation/trigger/#state-trigger
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Offer sun based automation rules.
|
Offer sun based automation rules.
|
||||||
|
|
||||||
For more details about this automation rule, please refer to the documentation
|
For more details about this automation rule, please refer to the documentation
|
||||||
at https://home-assistant.io/components/automation/#sun-trigger
|
at https://home-assistant.io/docs/automation/trigger/#sun-trigger
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Offer template automation rules.
|
Offer template automation rules.
|
||||||
|
|
||||||
For more details about this automation rule, please refer to the documentation
|
For more details about this automation rule, please refer to the documentation
|
||||||
at https://home-assistant.io/components/automation/#template-trigger
|
at https://home-assistant.io/docs/automation/trigger/#template-trigger
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Offer time listening automation rules.
|
Offer time listening automation rules.
|
||||||
|
|
||||||
For more details about this automation rule, please refer to the documentation
|
For more details about this automation rule, please refer to the documentation
|
||||||
at https://home-assistant.io/components/automation/#time-trigger
|
at https://home-assistant.io/docs/automation/trigger/#time-trigger
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Offer zone automation rules.
|
Offer zone automation rules.
|
||||||
|
|
||||||
For more details about this automation rule, please refer to the documentation
|
For more details about this automation rule, please refer to the documentation
|
||||||
at https://home-assistant.io/components/automation/#zone-trigger
|
at https://home-assistant.io/docs/automation/trigger/#zone-trigger
|
||||||
"""
|
"""
|
||||||
import asyncio
|
import asyncio
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
|
@ -48,7 +48,7 @@ class LiteJetLight(Light):
|
||||||
def _on_load_changed(self):
|
def _on_load_changed(self):
|
||||||
"""Called on a LiteJet thread when a load's state changes."""
|
"""Called on a LiteJet thread when a load's state changes."""
|
||||||
_LOGGER.debug("Updating due to notification for %s", self._name)
|
_LOGGER.debug("Updating due to notification for %s", self._name)
|
||||||
self._hass.async_add_job(self.async_update_ha_state(True))
|
self.schedule_update_ha_state(True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
|
|
|
@ -98,6 +98,7 @@ class MQTTRoomSensor(Entity):
|
||||||
|
|
||||||
self.hass.async_add_job(self.async_update_ha_state())
|
self.hass.async_add_job(self.async_update_ha_state())
|
||||||
|
|
||||||
|
@callback
|
||||||
def message_received(topic, payload, qos):
|
def message_received(topic, payload, qos):
|
||||||
"""A new MQTT message has been received."""
|
"""A new MQTT message has been received."""
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -47,12 +47,12 @@ class LiteJetSwitch(SwitchDevice):
|
||||||
def _on_switch_pressed(self):
|
def _on_switch_pressed(self):
|
||||||
_LOGGER.debug("Updating pressed for %s", self._name)
|
_LOGGER.debug("Updating pressed for %s", self._name)
|
||||||
self._state = True
|
self._state = True
|
||||||
self._hass.async_add_job(self.async_update_ha_state())
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def _on_switch_released(self):
|
def _on_switch_released(self):
|
||||||
_LOGGER.debug("Updating released for %s", self._name)
|
_LOGGER.debug("Updating released for %s", self._name)
|
||||||
self._state = False
|
self._state = False
|
||||||
self._hass.async_add_job(self.async_update_ha_state())
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
|
|
@ -29,7 +29,7 @@ from homeassistant.const import (
|
||||||
EVENT_TIME_CHANGED, MATCH_ALL, EVENT_HOMEASSISTANT_CLOSE,
|
EVENT_TIME_CHANGED, MATCH_ALL, EVENT_HOMEASSISTANT_CLOSE,
|
||||||
EVENT_SERVICE_REMOVED, __version__)
|
EVENT_SERVICE_REMOVED, __version__)
|
||||||
from homeassistant.exceptions import (
|
from homeassistant.exceptions import (
|
||||||
HomeAssistantError, InvalidEntityFormatError, ShuttingDown)
|
HomeAssistantError, InvalidEntityFormatError)
|
||||||
from homeassistant.util.async import (
|
from homeassistant.util.async import (
|
||||||
run_coroutine_threadsafe, run_callback_threadsafe)
|
run_coroutine_threadsafe, run_callback_threadsafe)
|
||||||
import homeassistant.util as util
|
import homeassistant.util as util
|
||||||
|
@ -37,12 +37,6 @@ import homeassistant.util.dt as dt_util
|
||||||
import homeassistant.util.location as location
|
import homeassistant.util.location as location
|
||||||
from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA
|
from homeassistant.util.unit_system import UnitSystem, METRIC_SYSTEM # NOQA
|
||||||
|
|
||||||
try:
|
|
||||||
import uvloop
|
|
||||||
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
DOMAIN = 'homeassistant'
|
DOMAIN = 'homeassistant'
|
||||||
|
|
||||||
# How long we wait for the result of a service call
|
# How long we wait for the result of a service call
|
||||||
|
@ -86,10 +80,6 @@ def async_loop_exception_handler(loop, context):
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
exception = context.get('exception')
|
exception = context.get('exception')
|
||||||
if exception:
|
if exception:
|
||||||
# Do not report on shutting down exceptions.
|
|
||||||
if isinstance(exception, ShuttingDown):
|
|
||||||
return
|
|
||||||
|
|
||||||
kwargs['exc_info'] = (type(exception), exception,
|
kwargs['exc_info'] = (type(exception), exception,
|
||||||
exception.__traceback__)
|
exception.__traceback__)
|
||||||
|
|
||||||
|
@ -123,7 +113,7 @@ class HomeAssistant(object):
|
||||||
self.loop.set_default_executor(self.executor)
|
self.loop.set_default_executor(self.executor)
|
||||||
self.loop.set_exception_handler(async_loop_exception_handler)
|
self.loop.set_exception_handler(async_loop_exception_handler)
|
||||||
self._pending_tasks = []
|
self._pending_tasks = []
|
||||||
self._track_task = False
|
self._track_task = True
|
||||||
self.bus = EventBus(self)
|
self.bus = EventBus(self)
|
||||||
self.services = ServiceRegistry(self)
|
self.services = ServiceRegistry(self)
|
||||||
self.states = StateMachine(self.bus, self.loop)
|
self.states = StateMachine(self.bus, self.loop)
|
||||||
|
@ -148,6 +138,7 @@ class HomeAssistant(object):
|
||||||
# Block until stopped
|
# Block until stopped
|
||||||
_LOGGER.info("Starting Home Assistant core loop")
|
_LOGGER.info("Starting Home Assistant core loop")
|
||||||
self.loop.run_forever()
|
self.loop.run_forever()
|
||||||
|
return self.exit_code
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.loop.create_task(self.async_stop())
|
self.loop.create_task(self.async_stop())
|
||||||
self.loop.run_forever()
|
self.loop.run_forever()
|
||||||
|
@ -165,9 +156,10 @@ class HomeAssistant(object):
|
||||||
|
|
||||||
# pylint: disable=protected-access
|
# pylint: disable=protected-access
|
||||||
self.loop._thread_ident = threading.get_ident()
|
self.loop._thread_ident = threading.get_ident()
|
||||||
_async_create_timer(self)
|
|
||||||
self.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
self.bus.async_fire(EVENT_HOMEASSISTANT_START)
|
||||||
|
yield from self.async_stop_track_tasks()
|
||||||
self.state = CoreState.running
|
self.state = CoreState.running
|
||||||
|
_async_create_timer(self)
|
||||||
|
|
||||||
def add_job(self, target: Callable[..., None], *args: Any) -> None:
|
def add_job(self, target: Callable[..., None], *args: Any) -> None:
|
||||||
"""Add job to the executor pool.
|
"""Add job to the executor pool.
|
||||||
|
@ -238,6 +230,8 @@ class HomeAssistant(object):
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_block_till_done(self):
|
def async_block_till_done(self):
|
||||||
"""Block till all pending work is done."""
|
"""Block till all pending work is done."""
|
||||||
|
assert self._track_task, 'Not tracking tasks'
|
||||||
|
|
||||||
# To flush out any call_soon_threadsafe
|
# To flush out any call_soon_threadsafe
|
||||||
yield from asyncio.sleep(0, loop=self.loop)
|
yield from asyncio.sleep(0, loop=self.loop)
|
||||||
|
|
||||||
|
@ -252,7 +246,8 @@ class HomeAssistant(object):
|
||||||
|
|
||||||
def stop(self) -> None:
|
def stop(self) -> None:
|
||||||
"""Stop Home Assistant and shuts down all threads."""
|
"""Stop Home Assistant and shuts down all threads."""
|
||||||
run_coroutine_threadsafe(self.async_stop(), self.loop)
|
self.loop.call_soon_threadsafe(
|
||||||
|
self.loop.create_task, self.async_stop())
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def async_stop(self, exit_code=0) -> None:
|
def async_stop(self, exit_code=0) -> None:
|
||||||
|
@ -368,10 +363,6 @@ class EventBus(object):
|
||||||
|
|
||||||
This method must be run in the event loop.
|
This method must be run in the event loop.
|
||||||
"""
|
"""
|
||||||
if event_type != EVENT_HOMEASSISTANT_STOP and \
|
|
||||||
self._hass.state == CoreState.stopping:
|
|
||||||
raise ShuttingDown("Home Assistant is shutting down")
|
|
||||||
|
|
||||||
listeners = self._listeners.get(event_type, [])
|
listeners = self._listeners.get(event_type, [])
|
||||||
|
|
||||||
# EVENT_HOMEASSISTANT_CLOSE should go only to his listeners
|
# EVENT_HOMEASSISTANT_CLOSE should go only to his listeners
|
||||||
|
|
|
@ -7,12 +7,6 @@ class HomeAssistantError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class ShuttingDown(HomeAssistantError):
|
|
||||||
"""When trying to change something during shutdown."""
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidEntityFormatError(HomeAssistantError):
|
class InvalidEntityFormatError(HomeAssistantError):
|
||||||
"""When an invalid formatted entity is encountered."""
|
"""When an invalid formatted entity is encountered."""
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,13 @@ import homeassistant.util.yaml as yaml
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED,
|
STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED,
|
||||||
EVENT_STATE_CHANGED, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE,
|
EVENT_STATE_CHANGED, EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE,
|
||||||
ATTR_DISCOVERED, SERVER_PORT, EVENT_HOMEASSISTANT_STOP)
|
ATTR_DISCOVERED, SERVER_PORT, EVENT_HOMEASSISTANT_CLOSE)
|
||||||
from homeassistant.components import sun, mqtt, recorder
|
from homeassistant.components import sun, mqtt, recorder
|
||||||
from homeassistant.components.http.auth import auth_middleware
|
from homeassistant.components.http.auth import auth_middleware
|
||||||
from homeassistant.components.http.const import (
|
from homeassistant.components.http.const import (
|
||||||
KEY_USE_X_FORWARDED_FOR, KEY_BANS_ENABLED, KEY_TRUSTED_NETWORKS)
|
KEY_USE_X_FORWARDED_FOR, KEY_BANS_ENABLED, KEY_TRUSTED_NETWORKS)
|
||||||
from homeassistant.util.async import run_callback_threadsafe
|
from homeassistant.util.async import (
|
||||||
|
run_callback_threadsafe, run_coroutine_threadsafe)
|
||||||
|
|
||||||
_TEST_INSTANCE_PORT = SERVER_PORT
|
_TEST_INSTANCE_PORT = SERVER_PORT
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
@ -58,15 +59,11 @@ def get_test_home_assistant():
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
stop_event.set()
|
stop_event.set()
|
||||||
|
|
||||||
orig_start = hass.start
|
|
||||||
orig_stop = hass.stop
|
orig_stop = hass.stop
|
||||||
|
|
||||||
@patch.object(hass.loop, 'run_forever')
|
|
||||||
@patch.object(hass.loop, 'close')
|
|
||||||
def start_hass(*mocks):
|
def start_hass(*mocks):
|
||||||
"""Helper to start hass."""
|
"""Helper to start hass."""
|
||||||
orig_start()
|
run_coroutine_threadsafe(hass.async_start(), loop=hass.loop).result()
|
||||||
hass.block_till_done()
|
|
||||||
|
|
||||||
def stop_hass():
|
def stop_hass():
|
||||||
"""Stop hass."""
|
"""Stop hass."""
|
||||||
|
@ -101,7 +98,6 @@ def async_test_home_assistant(loop):
|
||||||
return orig_async_add_job(target, *args)
|
return orig_async_add_job(target, *args)
|
||||||
|
|
||||||
hass.async_add_job = async_add_job
|
hass.async_add_job = async_add_job
|
||||||
hass.async_track_tasks()
|
|
||||||
|
|
||||||
hass.config.location_name = 'test home'
|
hass.config.location_name = 'test home'
|
||||||
hass.config.config_dir = get_test_config_dir()
|
hass.config.config_dir = get_test_config_dir()
|
||||||
|
@ -123,7 +119,11 @@ def async_test_home_assistant(loop):
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def mock_async_start():
|
def mock_async_start():
|
||||||
"""Start the mocking."""
|
"""Start the mocking."""
|
||||||
with patch('homeassistant.core._async_create_timer'):
|
# 1. We only mock time during tests
|
||||||
|
# 2. We want block_till_done that is called inside stop_track_tasks
|
||||||
|
with patch('homeassistant.core._async_create_timer'), \
|
||||||
|
patch.object(hass, 'async_stop_track_tasks',
|
||||||
|
hass.async_block_till_done):
|
||||||
yield from orig_start()
|
yield from orig_start()
|
||||||
|
|
||||||
hass.async_start = mock_async_start
|
hass.async_start = mock_async_start
|
||||||
|
@ -134,7 +134,7 @@ def async_test_home_assistant(loop):
|
||||||
global INST_COUNT
|
global INST_COUNT
|
||||||
INST_COUNT -= 1
|
INST_COUNT -= 1
|
||||||
|
|
||||||
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, clear_instance)
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, clear_instance)
|
||||||
|
|
||||||
return hass
|
return hass
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
"""The tests for the Event automation."""
|
"""The tests for the Event automation."""
|
||||||
|
import asyncio
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from homeassistant.core import callback
|
from homeassistant.const import EVENT_HOMEASSISTANT_START
|
||||||
from homeassistant.setup import setup_component
|
from homeassistant.core import callback, CoreState
|
||||||
|
from homeassistant.setup import setup_component, async_setup_component
|
||||||
import homeassistant.components.automation as automation
|
import homeassistant.components.automation as automation
|
||||||
|
|
||||||
from tests.common import get_test_home_assistant, mock_component
|
from tests.common import get_test_home_assistant, mock_component, mock_service
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=invalid-name
|
# pylint: disable=invalid-name
|
||||||
|
@ -92,3 +94,30 @@ class TestAutomationEvent(unittest.TestCase):
|
||||||
self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'})
|
self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'})
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
self.assertEqual(0, len(self.calls))
|
self.assertEqual(0, len(self.calls))
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_if_fires_on_event_with_data(hass):
|
||||||
|
"""Test the firing of events with data."""
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
hass.state = CoreState.not_running
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'event',
|
||||||
|
'event_type': EVENT_HOMEASSISTANT_START,
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert res
|
||||||
|
assert not automation.is_on(hass, 'automation.hello')
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
yield from hass.async_start()
|
||||||
|
assert automation.is_on(hass, 'automation.hello')
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
84
tests/components/automation/test_homeassistant.py
Normal file
84
tests/components/automation/test_homeassistant.py
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
"""The tests for the Event automation."""
|
||||||
|
import asyncio
|
||||||
|
from unittest.mock import patch, Mock
|
||||||
|
|
||||||
|
from homeassistant.core import CoreState
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
import homeassistant.components.automation as automation
|
||||||
|
|
||||||
|
from tests.common import mock_service, mock_coro
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_if_fires_on_hass_start(hass):
|
||||||
|
"""Test the firing when HASS starts."""
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
hass.state = CoreState.not_running
|
||||||
|
config = {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'homeassistant',
|
||||||
|
'event': 'start',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, config)
|
||||||
|
assert res
|
||||||
|
assert not automation.is_on(hass, 'automation.hello')
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
yield from hass.async_start()
|
||||||
|
assert automation.is_on(hass, 'automation.hello')
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
with patch('homeassistant.config.async_hass_config_yaml',
|
||||||
|
Mock(return_value=mock_coro(config))):
|
||||||
|
yield from hass.services.async_call(
|
||||||
|
automation.DOMAIN, automation.SERVICE_RELOAD, blocking=True)
|
||||||
|
|
||||||
|
assert automation.is_on(hass, 'automation.hello')
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def test_if_fires_on_hass_shutdown(hass):
|
||||||
|
"""Test the firing when HASS starts."""
|
||||||
|
calls = mock_service(hass, 'test', 'automation')
|
||||||
|
hass.state = CoreState.not_running
|
||||||
|
|
||||||
|
res = yield from async_setup_component(hass, automation.DOMAIN, {
|
||||||
|
automation.DOMAIN: {
|
||||||
|
'alias': 'hello',
|
||||||
|
'trigger': {
|
||||||
|
'platform': 'homeassistant',
|
||||||
|
'event': 'shutdown',
|
||||||
|
},
|
||||||
|
'action': {
|
||||||
|
'service': 'test.automation',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
assert res
|
||||||
|
assert not automation.is_on(hass, 'automation.hello')
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
yield from hass.async_start()
|
||||||
|
assert automation.is_on(hass, 'automation.hello')
|
||||||
|
assert len(calls) == 0
|
||||||
|
|
||||||
|
with patch.object(hass.loop, 'stop'):
|
||||||
|
yield from hass.async_stop()
|
||||||
|
assert len(calls) == 1
|
||||||
|
|
||||||
|
# with patch('homeassistant.config.async_hass_config_yaml',
|
||||||
|
# Mock(return_value=mock_coro(config))):
|
||||||
|
# yield from hass.services.async_call(
|
||||||
|
# automation.DOMAIN, automation.SERVICE_RELOAD, blocking=True)
|
||||||
|
|
||||||
|
# assert automation.is_on(hass, 'automation.hello')
|
||||||
|
# assert len(calls) == 1
|
|
@ -65,7 +65,7 @@ class TestFFmpegNoiseSetup(object):
|
||||||
entity = self.hass.states.get('binary_sensor.ffmpeg_noise')
|
entity = self.hass.states.get('binary_sensor.ffmpeg_noise')
|
||||||
assert entity.state == 'off'
|
assert entity.state == 'off'
|
||||||
|
|
||||||
mock_ffmpeg.call_args[0][2](True)
|
self.hass.add_job(mock_ffmpeg.call_args[0][2], True)
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
|
|
||||||
entity = self.hass.states.get('binary_sensor.ffmpeg_noise')
|
entity = self.hass.states.get('binary_sensor.ffmpeg_noise')
|
||||||
|
@ -130,7 +130,7 @@ class TestFFmpegMotionSetup(object):
|
||||||
entity = self.hass.states.get('binary_sensor.ffmpeg_motion')
|
entity = self.hass.states.get('binary_sensor.ffmpeg_motion')
|
||||||
assert entity.state == 'off'
|
assert entity.state == 'off'
|
||||||
|
|
||||||
mock_ffmpeg.call_args[0][2](True)
|
self.hass.add_job(mock_ffmpeg.call_args[0][2], True)
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
|
|
||||||
entity = self.hass.states.get('binary_sensor.ffmpeg_motion')
|
entity = self.hass.states.get('binary_sensor.ffmpeg_motion')
|
||||||
|
|
|
@ -166,7 +166,7 @@ class TestAlert(unittest.TestCase):
|
||||||
def test_noack(self):
|
def test_noack(self):
|
||||||
"""Test no ack feature."""
|
"""Test no ack feature."""
|
||||||
entity = alert.Alert(self.hass, *TEST_NOACK)
|
entity = alert.Alert(self.hass, *TEST_NOACK)
|
||||||
self.hass.async_add_job(entity.begin_alerting)
|
self.hass.add_job(entity.begin_alerting)
|
||||||
self.hass.block_till_done()
|
self.hass.block_till_done()
|
||||||
|
|
||||||
self.assertEqual(True, entity.hidden)
|
self.assertEqual(True, entity.hidden)
|
||||||
|
|
|
@ -1,49 +1,33 @@
|
||||||
"""Test Z-Wave node entity."""
|
"""Test Z-Wave node entity."""
|
||||||
|
import asyncio
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch, Mock
|
from unittest.mock import patch
|
||||||
from tests.common import get_test_home_assistant
|
|
||||||
import tests.mock.zwave as mock_zwave
|
import tests.mock.zwave as mock_zwave
|
||||||
import pytest
|
import pytest
|
||||||
from homeassistant.components.zwave import node_entity
|
from homeassistant.components.zwave import node_entity
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('mock_openzwave')
|
@asyncio.coroutine
|
||||||
class TestZWaveBaseEntity(unittest.TestCase):
|
def test_maybe_schedule_update(hass, mock_openzwave):
|
||||||
"""Class to test ZWaveBaseEntity."""
|
"""Test maybe schedule update."""
|
||||||
|
base_entity = node_entity.ZWaveBaseEntity()
|
||||||
|
base_entity.hass = hass
|
||||||
|
|
||||||
def setUp(self):
|
with patch.object(hass.loop, 'call_later') as mock_call_later:
|
||||||
"""Initialize values for this testcase class."""
|
base_entity._schedule_update()
|
||||||
self.hass = get_test_home_assistant()
|
assert mock_call_later.called
|
||||||
|
|
||||||
def call_soon(time, func, *args):
|
base_entity._schedule_update()
|
||||||
"""Replace call_later by call_soon."""
|
assert len(mock_call_later.mock_calls) == 1
|
||||||
return self.hass.loop.call_soon(func, *args)
|
|
||||||
|
|
||||||
self.hass.loop.call_later = call_soon
|
do_update = mock_call_later.mock_calls[0][1][1]
|
||||||
self.base_entity = node_entity.ZWaveBaseEntity()
|
|
||||||
self.base_entity.hass = self.hass
|
|
||||||
self.hass.start()
|
|
||||||
|
|
||||||
def tearDown(self): # pylint: disable=invalid-name
|
with patch.object(hass, 'async_add_job') as mock_add_job:
|
||||||
"""Stop everything that was started."""
|
do_update()
|
||||||
self.hass.stop()
|
assert mock_add_job.called
|
||||||
|
|
||||||
def test_maybe_schedule_update(self):
|
base_entity._schedule_update()
|
||||||
"""Test maybe_schedule_update."""
|
assert len(mock_call_later.mock_calls) == 2
|
||||||
with patch.object(self.base_entity, 'async_update_ha_state',
|
|
||||||
Mock()) as mock_update:
|
|
||||||
self.base_entity.maybe_schedule_update()
|
|
||||||
self.hass.block_till_done()
|
|
||||||
mock_update.assert_called_once_with()
|
|
||||||
|
|
||||||
def test_maybe_schedule_update_called_twice(self):
|
|
||||||
"""Test maybe_schedule_update called twice."""
|
|
||||||
with patch.object(self.base_entity, 'async_update_ha_state',
|
|
||||||
Mock()) as mock_update:
|
|
||||||
self.base_entity.maybe_schedule_update()
|
|
||||||
self.base_entity.maybe_schedule_update()
|
|
||||||
self.hass.block_till_done()
|
|
||||||
mock_update.assert_called_once_with()
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures('mock_openzwave')
|
@pytest.mark.usefixtures('mock_openzwave')
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue