diff --git a/.coveragerc b/.coveragerc index 2f1b0d1e857..9de3b6f8cf6 100644 --- a/.coveragerc +++ b/.coveragerc @@ -5,6 +5,7 @@ omit = homeassistant/__main__.py homeassistant/scripts/*.py homeassistant/helpers/typing.py + homeassistant/helpers/signal.py # omit pieces of code that rely on external devices being present homeassistant/components/apcupsd.py diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 0c8b0bc688e..87fd9f7d9e5 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -26,6 +26,7 @@ from homeassistant.const import EVENT_COMPONENT_LOADED, PLATFORM_FORMAT from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import ( event_decorators, service, config_per_platform, extract_domain_configs) +from homeassistant.helpers.signal import async_register_signal_handling _LOGGER = logging.getLogger(__name__) @@ -435,6 +436,7 @@ def async_from_config_dict(config: Dict[str, Any], yield from hass.async_stop_track_tasks() + async_register_signal_handling(hass) return hass diff --git a/homeassistant/core.py b/homeassistant/core.py index db186fb1b1c..5bede3da7a6 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -11,7 +11,6 @@ import enum import logging import os import re -import signal import sys import threading @@ -26,7 +25,7 @@ from homeassistant.const import ( ATTR_SERVICE_CALL_ID, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_SERVICE_EXECUTED, EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED, - EVENT_TIME_CHANGED, MATCH_ALL, RESTART_EXIT_CODE, __version__) + EVENT_TIME_CHANGED, MATCH_ALL, __version__) from homeassistant.exceptions import ( HomeAssistantError, InvalidEntityFormatError, ShuttingDown) from homeassistant.util.async import ( @@ -150,24 +149,6 @@ class HomeAssistant(object): _LOGGER.info("Starting Home Assistant") self.state = CoreState.starting - # Setup signal handling - if sys.platform != 'win32': - def _async_signal_handle(exit_code): - """Wrap signal handling.""" - self.async_add_job(self.async_stop(exit_code)) - - try: - self.loop.add_signal_handler( - signal.SIGTERM, _async_signal_handle, 0) - except ValueError: - _LOGGER.warning("Could not bind to SIGTERM") - - try: - self.loop.add_signal_handler( - signal.SIGHUP, _async_signal_handle, RESTART_EXIT_CODE) - except ValueError: - _LOGGER.warning("Could not bind to SIGHUP") - # pylint: disable=protected-access self.loop._thread_ident = threading.get_ident() _async_create_timer(self) diff --git a/homeassistant/helpers/signal.py b/homeassistant/helpers/signal.py new file mode 100644 index 00000000000..de4c344d375 --- /dev/null +++ b/homeassistant/helpers/signal.py @@ -0,0 +1,31 @@ +"""Signal handling related helpers.""" +import logging +import signal +import sys + +from homeassistant.core import callback +from homeassistant.const import RESTART_EXIT_CODE + +_LOGGER = logging.getLogger(__name__) + + +@callback +def async_register_signal_handling(hass): + """Register system signal handler for core.""" + if sys.platform != 'win32': + @callback + def async_signal_handle(exit_code): + """Wrap signal handling.""" + hass.async_add_job(hass.async_stop(exit_code)) + + try: + hass.loop.add_signal_handler( + signal.SIGTERM, async_signal_handle, 0) + except ValueError: + _LOGGER.warning("Could not bind to SIGTERM") + + try: + hass.loop.add_signal_handler( + signal.SIGHUP, async_signal_handle, RESTART_EXIT_CODE) + except ValueError: + _LOGGER.warning("Could not bind to SIGHUP") diff --git a/tests/common.py b/tests/common.py index 5ebca8640cc..e384a4f1b37 100644 --- a/tests/common.py +++ b/tests/common.py @@ -108,8 +108,7 @@ def async_test_home_assistant(loop): @asyncio.coroutine def mock_async_start(): """Start the mocking.""" - with patch.object(loop, 'add_signal_handler'), \ - patch('homeassistant.core._async_create_timer'): + with patch('homeassistant.core._async_create_timer'): yield from orig_start() hass.async_start = mock_async_start diff --git a/tests/helpers/test_discovery.py b/tests/helpers/test_discovery.py index 5d6faf2f7c4..fcbec64cc98 100644 --- a/tests/helpers/test_discovery.py +++ b/tests/helpers/test_discovery.py @@ -155,7 +155,8 @@ class TestHelpersDiscovery: assert 'test_component' in self.hass.config.components assert 'switch' in self.hass.config.components - def test_1st_discovers_2nd_component(self): + @patch('homeassistant.bootstrap.async_register_signal_handling') + def test_1st_discovers_2nd_component(self, mock_signal): """Test that we don't break if one component discovers the other. If the first component fires a discovery event to setup the diff --git a/tests/test_bootstrap.py b/tests/test_bootstrap.py index 887ef7c2c20..0a1e3633916 100644 --- a/tests/test_bootstrap.py +++ b/tests/test_bootstrap.py @@ -58,7 +58,8 @@ class TestBootstrap: autospec=True) @mock.patch('homeassistant.util.location.detect_location_info', autospec=True, return_value=None) - def test_from_config_file(self, mock_upgrade, mock_detect): + @mock.patch('homeassistant.helpers.signal.async_register_signal_handling') + def test_from_config_file(self, mock_upgrade, mock_detect, mock_signal): """Test with configuration file.""" components = ['browser', 'conversation', 'script'] files = { @@ -289,7 +290,8 @@ class TestBootstrap: assert 'comp' not in self.hass.config.components @mock.patch('homeassistant.bootstrap.enable_logging') - def test_home_assistant_core_config_validation(self, log_mock): + @mock.patch('homeassistant.helpers.signal.async_register_signal_handling') + def test_home_assistant_core_config_validation(self, log_mock, sig_mock): """Test if we pass in wrong information for HA conf.""" # Extensive HA conf validation testing is done in test_config.py assert None is bootstrap.from_config_dict({ @@ -393,7 +395,8 @@ class TestBootstrap: assert loader.get_component('disabled_component') is not None assert 'disabled_component' in self.hass.config.components - def test_all_work_done_before_start(self): + @mock.patch('homeassistant.bootstrap.async_register_signal_handling') + def test_all_work_done_before_start(self, signal_mock): """Test all init work done till start.""" call_order = []