Ensure scripts with timeouts of zero timeout immediately (#115830)
This commit is contained in:
parent
ebbcad17c6
commit
4529268544
2 changed files with 198 additions and 5 deletions
|
@ -650,6 +650,12 @@ class _ScriptRun:
|
|||
# check if condition already okay
|
||||
if condition.async_template(self._hass, wait_template, self._variables, False):
|
||||
self._variables["wait"]["completed"] = True
|
||||
self._changed()
|
||||
return
|
||||
|
||||
if timeout == 0:
|
||||
self._changed()
|
||||
self._async_handle_timeout()
|
||||
return
|
||||
|
||||
futures, timeout_handle, timeout_future = self._async_futures_with_timeout(
|
||||
|
@ -1078,6 +1084,11 @@ class _ScriptRun:
|
|||
self._variables["wait"] = {"remaining": timeout, "trigger": None}
|
||||
trace_set_result(wait=self._variables["wait"])
|
||||
|
||||
if timeout == 0:
|
||||
self._changed()
|
||||
self._async_handle_timeout()
|
||||
return
|
||||
|
||||
futures, timeout_handle, timeout_future = self._async_futures_with_timeout(
|
||||
timeout
|
||||
)
|
||||
|
@ -1108,6 +1119,14 @@ class _ScriptRun:
|
|||
futures, timeout_handle, timeout_future, remove_triggers
|
||||
)
|
||||
|
||||
def _async_handle_timeout(self) -> None:
|
||||
"""Handle timeout."""
|
||||
self._variables["wait"]["remaining"] = 0.0
|
||||
if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True):
|
||||
self._log(_TIMEOUT_MSG)
|
||||
trace_set_result(wait=self._variables["wait"], timeout=True)
|
||||
raise _AbortScript from TimeoutError()
|
||||
|
||||
async def _async_wait_with_optional_timeout(
|
||||
self,
|
||||
futures: list[asyncio.Future[None]],
|
||||
|
@ -1118,11 +1137,7 @@ class _ScriptRun:
|
|||
try:
|
||||
await asyncio.wait(futures, return_when=asyncio.FIRST_COMPLETED)
|
||||
if timeout_future and timeout_future.done():
|
||||
self._variables["wait"]["remaining"] = 0.0
|
||||
if not self._action.get(CONF_CONTINUE_ON_TIMEOUT, True):
|
||||
self._log(_TIMEOUT_MSG)
|
||||
trace_set_result(wait=self._variables["wait"], timeout=True)
|
||||
raise _AbortScript from TimeoutError()
|
||||
self._async_handle_timeout()
|
||||
finally:
|
||||
if timeout_future and not timeout_future.done() and timeout_handle:
|
||||
timeout_handle.cancel()
|
||||
|
|
|
@ -1311,6 +1311,184 @@ async def test_wait_timeout(
|
|||
assert_action_trace(expected_trace)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"timeout_param", [0, "{{ 0 }}", {"minutes": 0}, {"minutes": "{{ 0 }}"}]
|
||||
)
|
||||
async def test_wait_trigger_with_zero_timeout(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, timeout_param: int | str
|
||||
) -> None:
|
||||
"""Test the wait trigger with zero timeout option."""
|
||||
event = "test_event"
|
||||
events = async_capture_events(hass, event)
|
||||
action = {
|
||||
"wait_for_trigger": {
|
||||
"platform": "state",
|
||||
"entity_id": "switch.test",
|
||||
"to": "off",
|
||||
}
|
||||
}
|
||||
action["timeout"] = timeout_param
|
||||
action["continue_on_timeout"] = True
|
||||
sequence = cv.SCRIPT_SCHEMA([action, {"event": event}])
|
||||
sequence = await script.async_validate_actions_config(hass, sequence)
|
||||
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
|
||||
wait_started_flag = async_watch_for_action(script_obj, "wait")
|
||||
hass.states.async_set("switch.test", "on")
|
||||
hass.async_create_task(script_obj.async_run(context=Context()))
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(wait_started_flag.wait(), 1)
|
||||
except (AssertionError, TimeoutError):
|
||||
await script_obj.async_stop()
|
||||
raise
|
||||
|
||||
assert not script_obj.is_running
|
||||
assert len(events) == 1
|
||||
assert "(timeout: 0:00:00)" in caplog.text
|
||||
|
||||
variable_wait = {"wait": {"trigger": None, "remaining": 0.0}}
|
||||
expected_trace = {
|
||||
"0": [
|
||||
{
|
||||
"result": variable_wait,
|
||||
"variables": variable_wait,
|
||||
}
|
||||
],
|
||||
"1": [{"result": {"event": "test_event", "event_data": {}}}],
|
||||
}
|
||||
assert_action_trace(expected_trace)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"timeout_param", [0, "{{ 0 }}", {"minutes": 0}, {"minutes": "{{ 0 }}"}]
|
||||
)
|
||||
async def test_wait_trigger_matches_with_zero_timeout(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, timeout_param: int | str
|
||||
) -> None:
|
||||
"""Test the wait trigger that matches with zero timeout option."""
|
||||
event = "test_event"
|
||||
events = async_capture_events(hass, event)
|
||||
action = {
|
||||
"wait_for_trigger": {
|
||||
"platform": "state",
|
||||
"entity_id": "switch.test",
|
||||
"to": "off",
|
||||
}
|
||||
}
|
||||
action["timeout"] = timeout_param
|
||||
action["continue_on_timeout"] = True
|
||||
sequence = cv.SCRIPT_SCHEMA([action, {"event": event}])
|
||||
sequence = await script.async_validate_actions_config(hass, sequence)
|
||||
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
|
||||
wait_started_flag = async_watch_for_action(script_obj, "wait")
|
||||
hass.states.async_set("switch.test", "off")
|
||||
hass.async_create_task(script_obj.async_run(context=Context()))
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(wait_started_flag.wait(), 1)
|
||||
except (AssertionError, TimeoutError):
|
||||
await script_obj.async_stop()
|
||||
raise
|
||||
|
||||
assert not script_obj.is_running
|
||||
assert len(events) == 1
|
||||
assert "(timeout: 0:00:00)" in caplog.text
|
||||
|
||||
variable_wait = {"wait": {"trigger": None, "remaining": 0.0}}
|
||||
expected_trace = {
|
||||
"0": [
|
||||
{
|
||||
"result": variable_wait,
|
||||
"variables": variable_wait,
|
||||
}
|
||||
],
|
||||
"1": [{"result": {"event": "test_event", "event_data": {}}}],
|
||||
}
|
||||
assert_action_trace(expected_trace)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"timeout_param", [0, "{{ 0 }}", {"minutes": 0}, {"minutes": "{{ 0 }}"}]
|
||||
)
|
||||
async def test_wait_template_with_zero_timeout(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, timeout_param: int | str
|
||||
) -> None:
|
||||
"""Test the wait template with zero timeout option."""
|
||||
event = "test_event"
|
||||
events = async_capture_events(hass, event)
|
||||
action = {"wait_template": "{{ states.switch.test.state == 'off' }}"}
|
||||
action["timeout"] = timeout_param
|
||||
action["continue_on_timeout"] = True
|
||||
sequence = cv.SCRIPT_SCHEMA([action, {"event": event}])
|
||||
sequence = await script.async_validate_actions_config(hass, sequence)
|
||||
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
|
||||
wait_started_flag = async_watch_for_action(script_obj, "wait")
|
||||
hass.states.async_set("switch.test", "on")
|
||||
hass.async_create_task(script_obj.async_run(context=Context()))
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(wait_started_flag.wait(), 1)
|
||||
except (AssertionError, TimeoutError):
|
||||
await script_obj.async_stop()
|
||||
raise
|
||||
|
||||
assert not script_obj.is_running
|
||||
assert len(events) == 1
|
||||
assert "(timeout: 0:00:00)" in caplog.text
|
||||
variable_wait = {"wait": {"completed": False, "remaining": 0.0}}
|
||||
expected_trace = {
|
||||
"0": [
|
||||
{
|
||||
"result": variable_wait,
|
||||
"variables": variable_wait,
|
||||
}
|
||||
],
|
||||
"1": [{"result": {"event": "test_event", "event_data": {}}}],
|
||||
}
|
||||
assert_action_trace(expected_trace)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"timeout_param", [0, "{{ 0 }}", {"minutes": 0}, {"minutes": "{{ 0 }}"}]
|
||||
)
|
||||
async def test_wait_template_matches_with_zero_timeout(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, timeout_param: int | str
|
||||
) -> None:
|
||||
"""Test the wait template that matches with zero timeout option."""
|
||||
event = "test_event"
|
||||
events = async_capture_events(hass, event)
|
||||
action = {"wait_template": "{{ states.switch.test.state == 'off' }}"}
|
||||
action["timeout"] = timeout_param
|
||||
action["continue_on_timeout"] = True
|
||||
sequence = cv.SCRIPT_SCHEMA([action, {"event": event}])
|
||||
sequence = await script.async_validate_actions_config(hass, sequence)
|
||||
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
|
||||
wait_started_flag = async_watch_for_action(script_obj, "wait")
|
||||
hass.states.async_set("switch.test", "off")
|
||||
hass.async_create_task(script_obj.async_run(context=Context()))
|
||||
|
||||
try:
|
||||
await asyncio.wait_for(wait_started_flag.wait(), 1)
|
||||
except (AssertionError, TimeoutError):
|
||||
await script_obj.async_stop()
|
||||
raise
|
||||
|
||||
assert not script_obj.is_running
|
||||
assert len(events) == 1
|
||||
assert "(timeout: 0:00:00)" in caplog.text
|
||||
variable_wait = {"wait": {"completed": True, "remaining": 0.0}}
|
||||
expected_trace = {
|
||||
"0": [
|
||||
{
|
||||
"result": variable_wait,
|
||||
"variables": variable_wait,
|
||||
}
|
||||
],
|
||||
"1": [{"result": {"event": "test_event", "event_data": {}}}],
|
||||
}
|
||||
assert_action_trace(expected_trace)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("continue_on_timeout", "n_events"), [(False, 0), (True, 1), (None, 1)]
|
||||
)
|
||||
|
|
Loading…
Add table
Reference in a new issue