From 83b9c6188daa373a233ec5340cbd3819f1d4b76d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 21 Aug 2020 18:31:48 -0500 Subject: [PATCH] Make template entities reloadable (#39075) * Make template entities reloadable * Address review items --- homeassistant/components/template/__init__.py | 65 +++++ .../template/alarm_control_panel.py | 15 +- .../components/template/binary_sensor.py | 14 +- homeassistant/components/template/const.py | 6 + homeassistant/components/template/cover.py | 14 +- homeassistant/components/template/fan.py | 14 +- homeassistant/components/template/light.py | 14 +- homeassistant/components/template/lock.py | 38 +-- homeassistant/components/template/sensor.py | 15 +- .../components/template/services.yaml | 3 + homeassistant/components/template/switch.py | 14 +- .../components/template/template_entity.py | 7 +- homeassistant/components/template/vacuum.py | 14 +- .../components/template/test_binary_sensor.py | 3 + tests/components/template/test_init.py | 232 ++++++++++++++++++ tests/components/template/test_sensor.py | 3 + .../template/broken_configuration.yaml | 16 ++ .../template/configuration.yaml.corrupt | Bin 0 -> 2828 bytes .../template/empty_configuration.yaml | 1 + .../template/sensor_configuration.yaml | 23 ++ 20 files changed, 468 insertions(+), 43 deletions(-) create mode 100644 homeassistant/components/template/services.yaml create mode 100644 tests/components/template/test_init.py create mode 100644 tests/fixtures/template/broken_configuration.yaml create mode 100644 tests/fixtures/template/configuration.yaml.corrupt create mode 100644 tests/fixtures/template/empty_configuration.yaml create mode 100644 tests/fixtures/template/sensor_configuration.yaml diff --git a/homeassistant/components/template/__init__.py b/homeassistant/components/template/__init__.py index 0c205a0196c..f7f40cb92f7 100644 --- a/homeassistant/components/template/__init__.py +++ b/homeassistant/components/template/__init__.py @@ -1 +1,66 @@ """The template component.""" + +import logging + +from homeassistant import config as conf_util +from homeassistant.const import SERVICE_RELOAD +from homeassistant.exceptions import HomeAssistantError +from homeassistant.helpers import config_per_platform, entity_platform +from homeassistant.loader import async_get_integration + +from .const import DOMAIN, EVENT_TEMPLATE_RELOADED, PLATFORM_STORAGE_KEY + +_LOGGER = logging.getLogger(__name__) + + +async def _async_setup_reload_service(hass): + if hass.services.has_service(DOMAIN, SERVICE_RELOAD): + return + + async def _reload_config(call): + """Reload the template platform config.""" + + try: + unprocessed_conf = await conf_util.async_hass_config_yaml(hass) + except HomeAssistantError as err: + _LOGGER.error(err) + return + + for platform in hass.data[PLATFORM_STORAGE_KEY]: + + integration = await async_get_integration(hass, platform.domain) + + conf = await conf_util.async_process_component_config( + hass, unprocessed_conf, integration + ) + + if not conf: + continue + + await platform.async_reset() + + # Extract only the config for template, ignore the rest. + for p_type, p_config in config_per_platform(conf, platform.domain): + if p_type != DOMAIN: + continue + + entities = await platform.platform.async_create_entities(hass, p_config) + + await platform.async_add_entities(entities) + + hass.bus.async_fire(EVENT_TEMPLATE_RELOADED, context=call.context) + + hass.helpers.service.async_register_admin_service( + DOMAIN, SERVICE_RELOAD, _reload_config + ) + + +async def async_setup_platform_reloadable(hass): + """Template platform with reloadability.""" + + await _async_setup_reload_service(hass) + + platform = entity_platform.current_platform.get() + + if platform not in hass.data.setdefault(PLATFORM_STORAGE_KEY, []): + hass.data[PLATFORM_STORAGE_KEY].append(platform) diff --git a/homeassistant/components/template/alarm_control_panel.py b/homeassistant/components/template/alarm_control_panel.py index f60253a17bb..ac71ec74397 100644 --- a/homeassistant/components/template/alarm_control_panel.py +++ b/homeassistant/components/template/alarm_control_panel.py @@ -33,6 +33,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script +from . import async_setup_platform_reloadable from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) @@ -75,8 +76,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Template Alarm Control Panels.""" +async def async_create_entities(hass, config): + """Create Template Alarm Control Panels.""" + alarm_control_panels = [] for device, device_config in config[CONF_ALARM_CONTROL_PANELS].items(): @@ -104,7 +106,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) ) - async_add_entities(alarm_control_panels) + return alarm_control_panels + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Template Alarm Control Panels.""" + + await async_setup_platform_reloadable(hass) + async_add_entities(await async_create_entities(hass, config)) class AlarmControlPanelTemplate(TemplateEntity, AlarmControlPanelEntity): diff --git a/homeassistant/components/template/binary_sensor.py b/homeassistant/components/template/binary_sensor.py index c2b12f8a1bc..863bf2ab1c9 100644 --- a/homeassistant/components/template/binary_sensor.py +++ b/homeassistant/components/template/binary_sensor.py @@ -26,6 +26,7 @@ from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.event import async_call_later from homeassistant.helpers.template import result_as_boolean +from . import async_setup_platform_reloadable from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity @@ -56,8 +57,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up template binary sensors.""" +async def async_create_entities(hass, config): + """Create the template binary sensors.""" sensors = [] for device, device_config in config[CONF_SENSORS].items(): @@ -90,7 +91,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) ) - async_add_entities(sensors) + return sensors + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the template binary sensors.""" + + await async_setup_platform_reloadable(hass) + async_add_entities(await async_create_entities(hass, config)) class BinarySensorTemplate(TemplateEntity, BinarySensorEntity): diff --git a/homeassistant/components/template/const.py b/homeassistant/components/template/const.py index e6cf69341f9..6d46978b86f 100644 --- a/homeassistant/components/template/const.py +++ b/homeassistant/components/template/const.py @@ -1,3 +1,9 @@ """Constants for the Template Platform Components.""" CONF_AVAILABILITY_TEMPLATE = "availability_template" + +DOMAIN = "template" + +PLATFORM_STORAGE_KEY = "template_platforms" + +EVENT_TEMPLATE_RELOADED = "event_template_reloaded" diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index ec0853894a0..688b116628c 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -37,6 +37,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script +from . import async_setup_platform_reloadable from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity @@ -99,8 +100,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Template cover.""" +async def async_create_entities(hass, config): + """Create the Template cover.""" covers = [] for device, device_config in config[CONF_COVERS].items(): @@ -145,7 +146,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) ) - async_add_entities(covers) + return covers + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Template cover.""" + + await async_setup_platform_reloadable(hass) + async_add_entities(await async_create_entities(hass, config)) class CoverTemplate(TemplateEntity, CoverEntity): diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index a6f8741aad9..747d8b522a5 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -34,6 +34,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script +from . import async_setup_platform_reloadable from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity @@ -80,8 +81,8 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Template Fans.""" +async def async_create_entities(hass, config): + """Create the Template Fans.""" fans = [] for device, device_config in config[CONF_FANS].items(): @@ -122,7 +123,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) ) - async_add_entities(fans) + return fans + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the template fans.""" + + await async_setup_platform_reloadable(hass) + async_add_entities(await async_create_entities(hass, config)) class TemplateFan(TemplateEntity, FanEntity): diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 7945c56b3f5..c066a5d66d0 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -33,6 +33,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script +from . import async_setup_platform_reloadable from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity @@ -77,8 +78,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Template Lights.""" +async def async_create_entities(hass, config): + """Create the Template Lights.""" lights = [] for device, device_config in config[CONF_LIGHTS].items(): @@ -128,7 +129,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) ) - async_add_entities(lights) + return lights + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the template lights.""" + + await async_setup_platform_reloadable(hass) + async_add_entities(await async_create_entities(hass, config)) class LightTemplate(TemplateEntity, LightEntity): diff --git a/homeassistant/components/template/lock.py b/homeassistant/components/template/lock.py index 6097dcdb9e8..3e91e7b05c0 100644 --- a/homeassistant/components/template/lock.py +++ b/homeassistant/components/template/lock.py @@ -17,6 +17,7 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.script import Script +from . import async_setup_platform_reloadable from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity @@ -41,26 +42,31 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_devices, discovery_info=None): - """Set up the Template lock.""" +async def async_create_entities(hass, config): + """Create the Template lock.""" device = config.get(CONF_NAME) value_template = config.get(CONF_VALUE_TEMPLATE) availability_template = config.get(CONF_AVAILABILITY_TEMPLATE) - async_add_devices( - [ - TemplateLock( - hass, - device, - value_template, - availability_template, - config.get(CONF_LOCK), - config.get(CONF_UNLOCK), - config.get(CONF_OPTIMISTIC), - config.get(CONF_UNIQUE_ID), - ) - ] - ) + return [ + TemplateLock( + hass, + device, + value_template, + availability_template, + config.get(CONF_LOCK), + config.get(CONF_UNLOCK), + config.get(CONF_OPTIMISTIC), + config.get(CONF_UNIQUE_ID), + ) + ] + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the template lock.""" + + await async_setup_platform_reloadable(hass) + async_add_entities(await async_create_entities(hass, config)) class TemplateLock(TemplateEntity, LockEntity): diff --git a/homeassistant/components/template/sensor.py b/homeassistant/components/template/sensor.py index 895a8e2785b..da99ac40ed2 100644 --- a/homeassistant/components/template/sensor.py +++ b/homeassistant/components/template/sensor.py @@ -26,6 +26,7 @@ from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity, async_generate_entity_id +from . import async_setup_platform_reloadable from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity @@ -56,8 +57,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the template sensors.""" +async def async_create_entities(hass, config): + """Create the template sensors.""" + sensors = [] for device, device_config in config[CONF_SENSORS].items(): @@ -89,9 +91,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) ) - async_add_entities(sensors) + return sensors - return True + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the template sensors.""" + + await async_setup_platform_reloadable(hass) + async_add_entities(await async_create_entities(hass, config)) class SensorTemplate(TemplateEntity, Entity): diff --git a/homeassistant/components/template/services.yaml b/homeassistant/components/template/services.yaml new file mode 100644 index 00000000000..d36111d608e --- /dev/null +++ b/homeassistant/components/template/services.yaml @@ -0,0 +1,3 @@ +reload: + description: Reload all template entities. + diff --git a/homeassistant/components/template/switch.py b/homeassistant/components/template/switch.py index c31c89861eb..995f12584e6 100644 --- a/homeassistant/components/template/switch.py +++ b/homeassistant/components/template/switch.py @@ -26,6 +26,7 @@ from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.restore_state import RestoreEntity from homeassistant.helpers.script import Script +from . import async_setup_platform_reloadable from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity @@ -54,8 +55,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Template switch.""" +async def async_create_entities(hass, config): + """Create the Template switches.""" switches = [] for device, device_config in config[CONF_SWITCHES].items(): @@ -83,7 +84,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) ) - async_add_entities(switches) + return switches + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the template switches.""" + + await async_setup_platform_reloadable(hass) + async_add_entities(await async_create_entities(hass, config)) class SwitchTemplate(TemplateEntity, SwitchEntity, RestoreEntity): diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 406ee0f9953..a65f5cdd29f 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -5,7 +5,7 @@ from typing import Any, Callable, Optional, Union import voluptuous as vol -from homeassistant.core import EVENT_HOMEASSISTANT_START, callback +from homeassistant.core import EVENT_HOMEASSISTANT_START, CoreState, callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -232,7 +232,7 @@ class TemplateEntity(Entity): attribute.async_setup() self._template_attrs.append(attribute) - async def _async_template_startup(self, _) -> None: + async def _async_template_startup(self, *_) -> None: # async_update will not write state # until "add_complete" is set on the attribute for attribute in self._template_attrs: @@ -259,6 +259,9 @@ class TemplateEntity(Entity): self.add_template_attribute( "_entity_picture", self._entity_picture_template ) + if self.hass.state == CoreState.running: + await self._async_template_startup() + return self.hass.bus.async_listen_once( EVENT_HOMEASSISTANT_START, self._async_template_startup diff --git a/homeassistant/components/template/vacuum.py b/homeassistant/components/template/vacuum.py index ce6202bdc67..3fd2c0f6ad1 100644 --- a/homeassistant/components/template/vacuum.py +++ b/homeassistant/components/template/vacuum.py @@ -43,6 +43,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.script import Script +from . import async_setup_platform_reloadable from .const import CONF_AVAILABILITY_TEMPLATE from .template_entity import TemplateEntity @@ -92,8 +93,8 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend( ) -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the Template Vacuums.""" +async def async_create_entities(hass, config): + """Create the Template Vacuums.""" vacuums = [] for device, device_config in config[CONF_VACUUMS].items(): @@ -138,7 +139,14 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= ) ) - async_add_entities(vacuums) + return vacuums + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the template vacuums.""" + + await async_setup_platform_reloadable(hass) + async_add_entities(await async_create_entities(hass, config)) class TemplateVacuum(TemplateEntity, StateVacuumEntity): diff --git a/tests/components/template/test_binary_sensor.py b/tests/components/template/test_binary_sensor.py index 45bf0c5edb0..7003b55c7ed 100644 --- a/tests/components/template/test_binary_sensor.py +++ b/tests/components/template/test_binary_sensor.py @@ -11,6 +11,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.core import CoreState import homeassistant.util.dt as dt_util from tests.common import ( @@ -505,6 +506,8 @@ async def test_no_update_template_match_all(hass, caplog): """Test that we do not update sensors that match on all.""" hass.states.async_set("binary_sensor.test_sensor", "true") + hass.state = CoreState.not_running + await setup.async_setup_component( hass, "binary_sensor", diff --git a/tests/components/template/test_init.py b/tests/components/template/test_init.py new file mode 100644 index 00000000000..e655fd72987 --- /dev/null +++ b/tests/components/template/test_init.py @@ -0,0 +1,232 @@ +"""The test for the Template sensor platform.""" +from os import path +from unittest.mock import patch + +from homeassistant import config +from homeassistant.components.template import DOMAIN, SERVICE_RELOAD +from homeassistant.setup import async_setup_component + + +async def test_reloadable(hass): + """Test that we can reload.""" + hass.states.async_set("sensor.test_sensor", "mytest") + + await async_setup_component( + hass, + "sensor", + { + "sensor": { + "platform": DOMAIN, + "sensors": { + "state": { + "value_template": "{{ states.sensor.test_sensor.state }}" + }, + }, + } + }, + ) + await hass.async_block_till_done() + + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.get("sensor.state").state == "mytest" + assert len(hass.states.async_all()) == 2 + + yaml_path = path.join( + _get_fixtures_base_path(), "fixtures", "template/sensor_configuration.yaml", + ) + with patch.object(config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + DOMAIN, SERVICE_RELOAD, {}, blocking=True, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 3 + + assert hass.states.get("sensor.state") is None + assert hass.states.get("sensor.watching_tv_in_master_bedroom").state == "off" + assert float(hass.states.get("sensor.combined_sensor_energy_usage").state) == 0 + + +async def test_reloadable_can_remove(hass): + """Test that we can reload and remove all template sensors.""" + hass.states.async_set("sensor.test_sensor", "mytest") + + await async_setup_component( + hass, + "sensor", + { + "sensor": { + "platform": DOMAIN, + "sensors": { + "state": { + "value_template": "{{ states.sensor.test_sensor.state }}" + }, + }, + } + }, + ) + await hass.async_block_till_done() + + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.get("sensor.state").state == "mytest" + assert len(hass.states.async_all()) == 2 + + yaml_path = path.join( + _get_fixtures_base_path(), "fixtures", "template/empty_configuration.yaml", + ) + with patch.object(config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + DOMAIN, SERVICE_RELOAD, {}, blocking=True, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 1 + + +async def test_reloadable_stops_on_invalid_config(hass): + """Test we stop the reload if configuration.yaml is completely broken.""" + hass.states.async_set("sensor.test_sensor", "mytest") + + await async_setup_component( + hass, + "sensor", + { + "sensor": { + "platform": DOMAIN, + "sensors": { + "state": { + "value_template": "{{ states.sensor.test_sensor.state }}" + }, + }, + } + }, + ) + + await hass.async_block_till_done() + + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.get("sensor.state").state == "mytest" + assert len(hass.states.async_all()) == 2 + + yaml_path = path.join( + _get_fixtures_base_path(), "fixtures", "template/configuration.yaml.corrupt", + ) + with patch.object(config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + DOMAIN, SERVICE_RELOAD, {}, blocking=True, + ) + await hass.async_block_till_done() + + assert hass.states.get("sensor.state").state == "mytest" + assert len(hass.states.async_all()) == 2 + + +async def test_reloadable_handles_partial_valid_config(hass): + """Test we can still setup valid sensors when configuration.yaml has a broken entry.""" + hass.states.async_set("sensor.test_sensor", "mytest") + + await async_setup_component( + hass, + "sensor", + { + "sensor": { + "platform": DOMAIN, + "sensors": { + "state": { + "value_template": "{{ states.sensor.test_sensor.state }}" + }, + }, + } + }, + ) + + await hass.async_block_till_done() + + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.get("sensor.state").state == "mytest" + assert len(hass.states.async_all()) == 2 + + yaml_path = path.join( + _get_fixtures_base_path(), "fixtures", "template/broken_configuration.yaml", + ) + with patch.object(config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + DOMAIN, SERVICE_RELOAD, {}, blocking=True, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 3 + + assert hass.states.get("sensor.state") is None + assert hass.states.get("sensor.watching_tv_in_master_bedroom").state == "off" + assert float(hass.states.get("sensor.combined_sensor_energy_usage").state) == 0 + + +async def test_reloadable_multiple_platforms(hass): + """Test that we can reload.""" + hass.states.async_set("sensor.test_sensor", "mytest") + + await async_setup_component( + hass, + "sensor", + { + "sensor": { + "platform": DOMAIN, + "sensors": { + "state": { + "value_template": "{{ states.sensor.test_sensor.state }}" + }, + }, + } + }, + ) + await async_setup_component( + hass, + "binary_sensor", + { + "binary_sensor": { + "platform": DOMAIN, + "sensors": { + "state": { + "value_template": "{{ states.sensor.test_sensor.state }}" + }, + }, + } + }, + ) + await hass.async_block_till_done() + + await hass.async_start() + await hass.async_block_till_done() + + assert hass.states.get("sensor.state").state == "mytest" + assert hass.states.get("binary_sensor.state").state == "off" + + assert len(hass.states.async_all()) == 3 + + yaml_path = path.join( + _get_fixtures_base_path(), "fixtures", "template/sensor_configuration.yaml", + ) + with patch.object(config, "YAML_CONFIG_FILE", yaml_path): + await hass.services.async_call( + DOMAIN, SERVICE_RELOAD, {}, blocking=True, + ) + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 3 + + assert hass.states.get("sensor.state") is None + assert hass.states.get("sensor.watching_tv_in_master_bedroom").state == "off" + assert float(hass.states.get("sensor.combined_sensor_energy_usage").state) == 0 + + +def _get_fixtures_base_path(): + return path.dirname(path.dirname(path.dirname(__file__))) diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 08bf4650bba..3ffd9be4a64 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -10,6 +10,7 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) +from homeassistant.core import CoreState from homeassistant.setup import ATTR_COMPONENT, async_setup_component, setup_component from tests.common import assert_setup_component, get_test_home_assistant @@ -584,6 +585,8 @@ async def test_no_template_match_all(hass, caplog): """Test that we allow static templates.""" hass.states.async_set("sensor.test_sensor", "startup") + hass.state = CoreState.not_running + await async_setup_component( hass, "sensor", diff --git a/tests/fixtures/template/broken_configuration.yaml b/tests/fixtures/template/broken_configuration.yaml new file mode 100644 index 00000000000..9d21081ac87 --- /dev/null +++ b/tests/fixtures/template/broken_configuration.yaml @@ -0,0 +1,16 @@ +sensor: + - platform: template + broken: + - platform: template + sensors: + combined_sensor_energy_usage: + friendly_name: Combined Sense Energy Usage + unit_of_measurement: kW + value_template: '{{ ((states(''sensor.energy_usage'') | float) + (states(''sensor.energy_usage_2'') + | float)) / 1000 }}' + watching_tv_in_master_bedroom: + friendly_name: Watching TV in Master Bedroom + value_template: '{% if state_attr("remote.alexander_master_bedroom","current_activity") + == "Watch TV" or state_attr("remote.alexander_master_bedroom","current_activity") + == "Watch Apple TV" %}on{% else %}off{% endif %}' + diff --git a/tests/fixtures/template/configuration.yaml.corrupt b/tests/fixtures/template/configuration.yaml.corrupt new file mode 100644 index 0000000000000000000000000000000000000000..b30a14ec331c8107bde73203089439bb50d6b3b3 GIT binary patch literal 2828 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 U*^Aa!(j6wj*U#`Aw`t`90F~f(r~m)} literal 0 HcmV?d00001 diff --git a/tests/fixtures/template/empty_configuration.yaml b/tests/fixtures/template/empty_configuration.yaml new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/tests/fixtures/template/empty_configuration.yaml @@ -0,0 +1 @@ + diff --git a/tests/fixtures/template/sensor_configuration.yaml b/tests/fixtures/template/sensor_configuration.yaml new file mode 100644 index 00000000000..48ef4cf4304 --- /dev/null +++ b/tests/fixtures/template/sensor_configuration.yaml @@ -0,0 +1,23 @@ +sensor: + - platform: snmp + name: UPS kW + unit_of_measurement: kW + baseoid: 1.3.6.1.4.1.3808.1.1.1.4.2.5.0 + host: 192.168.210.25 + community: public + accept_errors: true + value_template: '{{ ((value | int) / 1000) | float | round(3) }}' + scan_interval: 900 + - platform: template + sensors: + combined_sensor_energy_usage: + friendly_name: Combined Sense Energy Usage + unit_of_measurement: kW + value_template: '{{ ((states(''sensor.energy_usage'') | float) + (states(''sensor.energy_usage_2'') + | float)) / 1000 }}' + watching_tv_in_master_bedroom: + friendly_name: Watching TV in Master Bedroom + value_template: '{% if state_attr("remote.alexander_master_bedroom","current_activity") + == "Watch TV" or state_attr("remote.alexander_master_bedroom","current_activity") + == "Watch Apple TV" %}on{% else %}off{% endif %}' +