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:
Paulus Schoutsen 2017-04-05 23:23:02 -07:00 committed by GitHub
parent 289d6b6605
commit 29f385ea76
23 changed files with 258 additions and 97 deletions

View file

@ -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:

View file

@ -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

View file

@ -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."""

View 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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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:

View file

@ -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):

View file

@ -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

View file

@ -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."""

View file

@ -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

View file

@ -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

View 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

View file

@ -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')

View file

@ -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)

View file

@ -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')