From 29f385ea76737baa523ac91ac74b323bb3d7dc65 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Wed, 5 Apr 2017 23:23:02 -0700 Subject: [PATCH] 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 --- homeassistant/__main__.py | 20 ++++- homeassistant/bootstrap.py | 6 +- homeassistant/components/automation/event.py | 19 ++++- .../components/automation/homeassistant.py | 55 ++++++++++++ .../components/automation/litejet.py | 4 +- homeassistant/components/automation/mqtt.py | 2 +- .../components/automation/numeric_state.py | 2 +- homeassistant/components/automation/state.py | 2 +- homeassistant/components/automation/sun.py | 2 +- .../components/automation/template.py | 2 +- homeassistant/components/automation/time.py | 2 +- homeassistant/components/automation/zone.py | 2 +- homeassistant/components/light/litejet.py | 2 +- homeassistant/components/sensor/mqtt_room.py | 1 + homeassistant/components/switch/litejet.py | 4 +- homeassistant/core.py | 27 ++---- homeassistant/exceptions.py | 6 -- tests/common.py | 20 ++--- tests/components/automation/test_event.py | 35 +++++++- .../automation/test_homeassistant.py | 84 +++++++++++++++++++ tests/components/binary_sensor/test_ffmpeg.py | 4 +- tests/components/test_alert.py | 2 +- tests/components/zwave/test_node_entity.py | 52 ++++-------- 23 files changed, 258 insertions(+), 97 deletions(-) create mode 100644 homeassistant/components/automation/homeassistant.py create mode 100644 tests/components/automation/test_homeassistant.py diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 1d5da4e798f..7035b26f670 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -20,6 +20,17 @@ from homeassistant.const import ( 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(): """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 ) - hass.start() - return hass.exit_code + return hass.start() def try_to_restart() -> None: @@ -359,11 +369,13 @@ def try_to_restart() -> None: def main() -> int: """Start Home Assistant.""" + validate_python() + + attempt_use_uvloop() + if sys.version_info[:3] < (3, 5, 3): monkey_patch_asyncio() - validate_python() - args = get_arguments() if args.script is not None: diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index e05fff98865..4c586d12ccd 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -74,8 +74,6 @@ def async_from_config_dict(config: Dict[str, Any], This method is a coroutine. """ start = time() - hass.async_track_tasks() - core_config = config.get(core.DOMAIN, {}) try: @@ -140,10 +138,10 @@ def async_from_config_dict(config: Dict[str, Any], continue 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() - _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) return hass diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index 21bf243e34f..0ff10665eb3 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -2,15 +2,15 @@ Offer event listening automation rules. 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 logging import voluptuous as vol -from homeassistant.core import callback -from homeassistant.const import CONF_PLATFORM +from homeassistant.core import callback, CoreState +from homeassistant.const import CONF_PLATFORM, EVENT_HOMEASSISTANT_START from homeassistant.helpers import config_validation as cv CONF_EVENT_TYPE = "event_type" @@ -31,6 +31,19 @@ def async_trigger(hass, config, action): event_type = config.get(CONF_EVENT_TYPE) 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 def handle_event(event): """Listen for events and calls the action when data matches.""" diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py new file mode 100644 index 00000000000..0222ef02c26 --- /dev/null +++ b/homeassistant/components/automation/homeassistant.py @@ -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 diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py index 56109e27f1b..c827fe8f7a4 100644 --- a/homeassistant/components/automation/litejet.py +++ b/homeassistant/components/automation/litejet.py @@ -70,7 +70,7 @@ def async_trigger(hass, config, action): nonlocal held_less_than, held_more_than pressed_time = dt_util.utcnow() 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: cancel_pressed_more_than = track_point_in_utc_time( hass, @@ -88,7 +88,7 @@ def async_trigger(hass, config, action): held_time = dt_util.utcnow() - pressed_time 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: - call_action() + hass.add_job(call_action) hass.data['litejet_system'].on_switch_pressed(number, pressed) hass.data['litejet_system'].on_switch_released(number, released) diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index fbea2cede38..172a368225d 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -2,7 +2,7 @@ Offer MQTT listening automation rules. 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 json diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index 8b3c3e57670..3657724f679 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -2,7 +2,7 @@ Offer numeric state listening automation rules. 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 logging diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index fdc46079263..1f55ef67f25 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -2,7 +2,7 @@ Offer state listening automation rules. 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 voluptuous as vol diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 4529b5a8b60..3ce84d60a91 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -2,7 +2,7 @@ Offer sun based automation rules. 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 from datetime import timedelta diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index a83671d5fa8..0fcdeaae5e0 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -2,7 +2,7 @@ Offer template automation rules. 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 logging diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py index e33fd0f6ba9..0adcd5f8272 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -2,7 +2,7 @@ Offer time listening automation rules. 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 logging diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index 8ffc0498317..c2a0e4d094d 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -2,7 +2,7 @@ Offer zone automation rules. 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 voluptuous as vol diff --git a/homeassistant/components/light/litejet.py b/homeassistant/components/light/litejet.py index 6534d180262..907d7f27fb9 100644 --- a/homeassistant/components/light/litejet.py +++ b/homeassistant/components/light/litejet.py @@ -48,7 +48,7 @@ class LiteJetLight(Light): def _on_load_changed(self): """Called on a LiteJet thread when a load's state changes.""" _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 def supported_features(self): diff --git a/homeassistant/components/sensor/mqtt_room.py b/homeassistant/components/sensor/mqtt_room.py index 432fff67802..427daa1a8a2 100644 --- a/homeassistant/components/sensor/mqtt_room.py +++ b/homeassistant/components/sensor/mqtt_room.py @@ -98,6 +98,7 @@ class MQTTRoomSensor(Entity): self.hass.async_add_job(self.async_update_ha_state()) + @callback def message_received(topic, payload, qos): """A new MQTT message has been received.""" try: diff --git a/homeassistant/components/switch/litejet.py b/homeassistant/components/switch/litejet.py index 4ea565ff62f..87a82ed67f2 100644 --- a/homeassistant/components/switch/litejet.py +++ b/homeassistant/components/switch/litejet.py @@ -47,12 +47,12 @@ class LiteJetSwitch(SwitchDevice): def _on_switch_pressed(self): _LOGGER.debug("Updating pressed for %s", self._name) self._state = True - self._hass.async_add_job(self.async_update_ha_state()) + self.schedule_update_ha_state() def _on_switch_released(self): _LOGGER.debug("Updating released for %s", self._name) self._state = False - self._hass.async_add_job(self.async_update_ha_state()) + self.schedule_update_ha_state() @property def name(self): diff --git a/homeassistant/core.py b/homeassistant/core.py index 1aba2db56f5..7c4ff43bd7a 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -29,7 +29,7 @@ from homeassistant.const import ( EVENT_TIME_CHANGED, MATCH_ALL, EVENT_HOMEASSISTANT_CLOSE, EVENT_SERVICE_REMOVED, __version__) from homeassistant.exceptions import ( - HomeAssistantError, InvalidEntityFormatError, ShuttingDown) + HomeAssistantError, InvalidEntityFormatError) from homeassistant.util.async import ( run_coroutine_threadsafe, run_callback_threadsafe) import homeassistant.util as util @@ -37,12 +37,6 @@ import homeassistant.util.dt as dt_util import homeassistant.util.location as location 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' # How long we wait for the result of a service call @@ -86,10 +80,6 @@ def async_loop_exception_handler(loop, context): kwargs = {} exception = context.get('exception') if exception: - # Do not report on shutting down exceptions. - if isinstance(exception, ShuttingDown): - return - kwargs['exc_info'] = (type(exception), exception, exception.__traceback__) @@ -123,7 +113,7 @@ class HomeAssistant(object): self.loop.set_default_executor(self.executor) self.loop.set_exception_handler(async_loop_exception_handler) self._pending_tasks = [] - self._track_task = False + self._track_task = True self.bus = EventBus(self) self.services = ServiceRegistry(self) self.states = StateMachine(self.bus, self.loop) @@ -148,6 +138,7 @@ class HomeAssistant(object): # Block until stopped _LOGGER.info("Starting Home Assistant core loop") self.loop.run_forever() + return self.exit_code except KeyboardInterrupt: self.loop.create_task(self.async_stop()) self.loop.run_forever() @@ -165,9 +156,10 @@ class HomeAssistant(object): # pylint: disable=protected-access self.loop._thread_ident = threading.get_ident() - _async_create_timer(self) self.bus.async_fire(EVENT_HOMEASSISTANT_START) + yield from self.async_stop_track_tasks() self.state = CoreState.running + _async_create_timer(self) def add_job(self, target: Callable[..., None], *args: Any) -> None: """Add job to the executor pool. @@ -238,6 +230,8 @@ class HomeAssistant(object): @asyncio.coroutine def async_block_till_done(self): """Block till all pending work is done.""" + assert self._track_task, 'Not tracking tasks' + # To flush out any call_soon_threadsafe yield from asyncio.sleep(0, loop=self.loop) @@ -252,7 +246,8 @@ class HomeAssistant(object): def stop(self) -> None: """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 def async_stop(self, exit_code=0) -> None: @@ -368,10 +363,6 @@ class EventBus(object): 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, []) # EVENT_HOMEASSISTANT_CLOSE should go only to his listeners diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py index f1ed646b02d..f45fd3c3841 100644 --- a/homeassistant/exceptions.py +++ b/homeassistant/exceptions.py @@ -7,12 +7,6 @@ class HomeAssistantError(Exception): pass -class ShuttingDown(HomeAssistantError): - """When trying to change something during shutdown.""" - - pass - - class InvalidEntityFormatError(HomeAssistantError): """When an invalid formatted entity is encountered.""" diff --git a/tests/common.py b/tests/common.py index 01889af1bf1..9dc077dc3f7 100644 --- a/tests/common.py +++ b/tests/common.py @@ -23,12 +23,13 @@ import homeassistant.util.yaml as yaml from homeassistant.const import ( STATE_ON, STATE_OFF, DEVICE_DEFAULT_NAME, EVENT_TIME_CHANGED, 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.http.auth import auth_middleware from homeassistant.components.http.const import ( 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 _LOGGER = logging.getLogger(__name__) @@ -58,15 +59,11 @@ def get_test_home_assistant(): loop.run_forever() stop_event.set() - orig_start = hass.start orig_stop = hass.stop - @patch.object(hass.loop, 'run_forever') - @patch.object(hass.loop, 'close') def start_hass(*mocks): """Helper to start hass.""" - orig_start() - hass.block_till_done() + run_coroutine_threadsafe(hass.async_start(), loop=hass.loop).result() def stop_hass(): """Stop hass.""" @@ -101,7 +98,6 @@ def async_test_home_assistant(loop): return orig_async_add_job(target, *args) hass.async_add_job = async_add_job - hass.async_track_tasks() hass.config.location_name = 'test home' hass.config.config_dir = get_test_config_dir() @@ -123,7 +119,11 @@ def async_test_home_assistant(loop): @asyncio.coroutine def mock_async_start(): """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() hass.async_start = mock_async_start @@ -134,7 +134,7 @@ def async_test_home_assistant(loop): global INST_COUNT 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 diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py index b4686650057..a056520a5c9 100644 --- a/tests/components/automation/test_event.py +++ b/tests/components/automation/test_event.py @@ -1,11 +1,13 @@ """The tests for the Event automation.""" +import asyncio import unittest -from homeassistant.core import callback -from homeassistant.setup import setup_component +from homeassistant.const import EVENT_HOMEASSISTANT_START +from homeassistant.core import callback, CoreState +from homeassistant.setup import setup_component, async_setup_component 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 @@ -92,3 +94,30 @@ class TestAutomationEvent(unittest.TestCase): self.hass.bus.fire('test_event', {'some_attr': 'some_other_value'}) self.hass.block_till_done() 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 diff --git a/tests/components/automation/test_homeassistant.py b/tests/components/automation/test_homeassistant.py new file mode 100644 index 00000000000..d63e59b3f54 --- /dev/null +++ b/tests/components/automation/test_homeassistant.py @@ -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 diff --git a/tests/components/binary_sensor/test_ffmpeg.py b/tests/components/binary_sensor/test_ffmpeg.py index 64c540f4398..aadafadd4a6 100644 --- a/tests/components/binary_sensor/test_ffmpeg.py +++ b/tests/components/binary_sensor/test_ffmpeg.py @@ -65,7 +65,7 @@ class TestFFmpegNoiseSetup(object): entity = self.hass.states.get('binary_sensor.ffmpeg_noise') 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() 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') 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() entity = self.hass.states.get('binary_sensor.ffmpeg_motion') diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py index 7fc25068732..8150d08ff72 100644 --- a/tests/components/test_alert.py +++ b/tests/components/test_alert.py @@ -166,7 +166,7 @@ class TestAlert(unittest.TestCase): def test_noack(self): """Test no ack feature.""" 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.assertEqual(True, entity.hidden) diff --git a/tests/components/zwave/test_node_entity.py b/tests/components/zwave/test_node_entity.py index 385677b6d97..c171155404f 100644 --- a/tests/components/zwave/test_node_entity.py +++ b/tests/components/zwave/test_node_entity.py @@ -1,49 +1,33 @@ """Test Z-Wave node entity.""" +import asyncio import unittest -from unittest.mock import patch, Mock -from tests.common import get_test_home_assistant +from unittest.mock import patch import tests.mock.zwave as mock_zwave import pytest from homeassistant.components.zwave import node_entity -@pytest.mark.usefixtures('mock_openzwave') -class TestZWaveBaseEntity(unittest.TestCase): - """Class to test ZWaveBaseEntity.""" +@asyncio.coroutine +def test_maybe_schedule_update(hass, mock_openzwave): + """Test maybe schedule update.""" + base_entity = node_entity.ZWaveBaseEntity() + base_entity.hass = hass - def setUp(self): - """Initialize values for this testcase class.""" - self.hass = get_test_home_assistant() + with patch.object(hass.loop, 'call_later') as mock_call_later: + base_entity._schedule_update() + assert mock_call_later.called - def call_soon(time, func, *args): - """Replace call_later by call_soon.""" - return self.hass.loop.call_soon(func, *args) + base_entity._schedule_update() + assert len(mock_call_later.mock_calls) == 1 - self.hass.loop.call_later = call_soon - self.base_entity = node_entity.ZWaveBaseEntity() - self.base_entity.hass = self.hass - self.hass.start() + do_update = mock_call_later.mock_calls[0][1][1] - def tearDown(self): # pylint: disable=invalid-name - """Stop everything that was started.""" - self.hass.stop() + with patch.object(hass, 'async_add_job') as mock_add_job: + do_update() + assert mock_add_job.called - def test_maybe_schedule_update(self): - """Test maybe_schedule_update.""" - 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() + base_entity._schedule_update() + assert len(mock_call_later.mock_calls) == 2 @pytest.mark.usefixtures('mock_openzwave')