diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 77c842a27fe..48a662e3a81 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -62,11 +62,7 @@ from homeassistant.core import ( callback, ) from homeassistant.helpers import condition, config_validation as cv, service, template -from homeassistant.helpers.event import ( - TrackTemplate, - async_call_later, - async_track_template_result, -) +from homeassistant.helpers.event import async_call_later, async_track_template from homeassistant.helpers.script_variables import ScriptVariables from homeassistant.helpers.trigger import ( async_initialize_triggers, @@ -359,7 +355,7 @@ class _ScriptRun: return @callback - def _async_script_wait(event, updates): + def async_script_wait(entity_id, from_s, to_s): """Handle script after template condition is true.""" self._variables["wait"] = { "remaining": to_context.remaining if to_context else delay, @@ -368,12 +364,9 @@ class _ScriptRun: done.set() to_context = None - info = async_track_template_result( - self._hass, - [TrackTemplate(wait_template, self._variables)], - _async_script_wait, + unsub = async_track_template( + self._hass, wait_template, async_script_wait, self._variables ) - unsub = info.async_remove self._changed() done = asyncio.Event() diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index 5be5f6bc91f..18e510b7582 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -8,6 +8,7 @@ from types import MappingProxyType from unittest import mock from unittest.mock import patch +from async_timeout import timeout import pytest import voluptuous as vol @@ -544,6 +545,41 @@ async def test_wait_basic(hass, action_type): assert script_obj.last_action is None +@pytest.mark.parametrize("action_type", ["template", "trigger"]) +async def test_wait_basic_times_out(hass, action_type): + """Test wait actions times out when the action does not happen.""" + wait_alias = "wait step" + action = {"alias": wait_alias} + if action_type == "template": + action["wait_template"] = "{{ states.switch.test.state == 'off' }}" + else: + action["wait_for_trigger"] = { + "platform": "state", + "entity_id": "switch.test", + "to": "off", + } + sequence = cv.SCRIPT_SCHEMA(action) + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + wait_started_flag = async_watch_for_action(script_obj, wait_alias) + timed_out = False + + try: + hass.states.async_set("switch.test", "on") + hass.async_create_task(script_obj.async_run(context=Context())) + await asyncio.wait_for(wait_started_flag.wait(), 1) + assert script_obj.is_running + assert script_obj.last_action == wait_alias + hass.states.async_set("switch.test", "not_on") + + with timeout(0.1): + await hass.async_block_till_done() + except asyncio.TimeoutError: + timed_out = True + await script_obj.async_stop() + + assert timed_out + + @pytest.mark.parametrize("action_type", ["template", "trigger"]) async def test_multiple_runs_wait(hass, action_type): """Test multiple runs with wait in script.""" @@ -782,30 +818,53 @@ async def test_wait_template_variables_in(hass): async def test_wait_template_with_utcnow(hass): """Test the wait template with utcnow.""" - sequence = cv.SCRIPT_SCHEMA({"wait_template": "{{ utcnow().hours == 12 }}"}) + sequence = cv.SCRIPT_SCHEMA({"wait_template": "{{ utcnow().hour == 12 }}"}) script_obj = script.Script(hass, sequence, "Test Name", "test_domain") wait_started_flag = async_watch_for_action(script_obj, "wait") - start_time = dt_util.utcnow() + timedelta(hours=24) + start_time = dt_util.utcnow().replace(minute=1) + timedelta(hours=48) try: hass.async_create_task(script_obj.async_run(context=Context())) - async_fire_time_changed(hass, start_time.replace(hour=5)) - assert not script_obj.is_running - async_fire_time_changed(hass, start_time.replace(hour=12)) - await asyncio.wait_for(wait_started_flag.wait(), 1) - assert script_obj.is_running + + match_time = start_time.replace(hour=12) + with patch("homeassistant.util.dt.utcnow", return_value=match_time): + async_fire_time_changed(hass, match_time) except (AssertionError, asyncio.TimeoutError): await script_obj.async_stop() raise else: - async_fire_time_changed(hass, start_time.replace(hour=3)) await hass.async_block_till_done() - assert not script_obj.is_running +async def test_wait_template_with_utcnow_no_match(hass): + """Test the wait template with utcnow that does not match.""" + sequence = cv.SCRIPT_SCHEMA({"wait_template": "{{ utcnow().hour == 12 }}"}) + script_obj = script.Script(hass, sequence, "Test Name", "test_domain") + wait_started_flag = async_watch_for_action(script_obj, "wait") + start_time = dt_util.utcnow().replace(minute=1) + timedelta(hours=48) + timed_out = False + + try: + hass.async_create_task(script_obj.async_run(context=Context())) + await asyncio.wait_for(wait_started_flag.wait(), 1) + assert script_obj.is_running + + non_maching_time = start_time.replace(hour=3) + with patch("homeassistant.util.dt.utcnow", return_value=non_maching_time): + async_fire_time_changed(hass, non_maching_time) + + with timeout(0.1): + await hass.async_block_till_done() + except asyncio.TimeoutError: + timed_out = True + await script_obj.async_stop() + + assert timed_out + + @pytest.mark.parametrize("mode", ["no_timeout", "timeout_finish", "timeout_not_finish"]) @pytest.mark.parametrize("action_type", ["template", "trigger"]) async def test_wait_variables_out(hass, mode, action_type):