Ensure multiple executions of a restart automation in the same event loop iteration are allowed (#119100)
* Add test for restarting automation related issue #119097 * fix * add a delay since restart is an infinite loop * testspull/119376/head
parent
a1f2140ed7
commit
87f48b15d1
|
@ -1758,10 +1758,6 @@ class Script:
|
|||
# runs before sleeping as otherwise if two runs are started at the exact
|
||||
# same time they will cancel each other out.
|
||||
self._log("Restarting")
|
||||
# Important: yield to the event loop to allow the script to start in case
|
||||
# the script is restarting itself so it ends up in the script stack and
|
||||
# the recursion check above will prevent the script from running.
|
||||
await asyncio.sleep(0)
|
||||
await self.async_stop(update_state=False, spare=run)
|
||||
|
||||
if started_action:
|
||||
|
|
|
@ -2771,6 +2771,7 @@ async def test_recursive_automation_starting_script(
|
|||
],
|
||||
"action": [
|
||||
{"service": "test.automation_started"},
|
||||
{"delay": 0.001},
|
||||
{"service": "script.script1"},
|
||||
],
|
||||
}
|
||||
|
@ -2817,7 +2818,10 @@ async def test_recursive_automation_starting_script(
|
|||
assert script_warning_msg in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize("automation_mode", SCRIPT_MODE_CHOICES)
|
||||
@pytest.mark.parametrize(
|
||||
"automation_mode",
|
||||
[mode for mode in SCRIPT_MODE_CHOICES if mode != SCRIPT_MODE_RESTART],
|
||||
)
|
||||
@pytest.mark.parametrize("wait_for_stop_scripts_after_shutdown", [True])
|
||||
async def test_recursive_automation(
|
||||
hass: HomeAssistant, automation_mode, caplog: pytest.LogCaptureFixture
|
||||
|
@ -2878,6 +2882,68 @@ async def test_recursive_automation(
|
|||
assert "Disallowed recursion detected" not in caplog.text
|
||||
|
||||
|
||||
@pytest.mark.parametrize("wait_for_stop_scripts_after_shutdown", [True])
|
||||
async def test_recursive_automation_restart_mode(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test automation restarting itself.
|
||||
|
||||
The automation is an infinite loop since it keeps restarting itself
|
||||
|
||||
- Illegal recursion detection should not be triggered
|
||||
- Home Assistant should not hang on shut down
|
||||
"""
|
||||
stop_scripts_at_shutdown_called = asyncio.Event()
|
||||
real_stop_scripts_at_shutdown = _async_stop_scripts_at_shutdown
|
||||
|
||||
async def stop_scripts_at_shutdown(*args):
|
||||
await real_stop_scripts_at_shutdown(*args)
|
||||
stop_scripts_at_shutdown_called.set()
|
||||
|
||||
with patch(
|
||||
"homeassistant.helpers.script._async_stop_scripts_at_shutdown",
|
||||
wraps=stop_scripts_at_shutdown,
|
||||
):
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: {
|
||||
"mode": SCRIPT_MODE_RESTART,
|
||||
"trigger": [
|
||||
{"platform": "event", "event_type": "trigger_automation"},
|
||||
],
|
||||
"action": [
|
||||
{"event": "trigger_automation"},
|
||||
{"service": "test.automation_done"},
|
||||
],
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
service_called = asyncio.Event()
|
||||
|
||||
async def async_service_handler(service):
|
||||
if service.service == "automation_done":
|
||||
service_called.set()
|
||||
|
||||
hass.services.async_register("test", "automation_done", async_service_handler)
|
||||
|
||||
hass.bus.async_fire("trigger_automation")
|
||||
await asyncio.sleep(0)
|
||||
|
||||
# Trigger 1st stage script shutdown
|
||||
hass.set_state(CoreState.stopping)
|
||||
hass.bus.async_fire("homeassistant_stop")
|
||||
await asyncio.wait_for(stop_scripts_at_shutdown_called.wait(), 1)
|
||||
|
||||
# Trigger 2nd stage script shutdown
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + timedelta(seconds=90))
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert "Disallowed recursion detected" not in caplog.text
|
||||
|
||||
|
||||
async def test_websocket_config(
|
||||
hass: HomeAssistant, hass_ws_client: WebSocketGenerator
|
||||
) -> None:
|
||||
|
@ -3097,3 +3163,72 @@ async def test_two_automations_call_restart_script_same_time(
|
|||
await hass.async_block_till_done()
|
||||
assert len(events) == 2
|
||||
cancel()
|
||||
|
||||
|
||||
async def test_two_automation_call_restart_script_right_after_each_other(
|
||||
hass: HomeAssistant,
|
||||
) -> None:
|
||||
"""Test two automations call a restart script right after each other."""
|
||||
|
||||
events = async_capture_events(hass, "repeat_test_script_finished")
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
input_boolean.DOMAIN,
|
||||
{
|
||||
input_boolean.DOMAIN: {
|
||||
"test_1": None,
|
||||
"test_2": None,
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
automation.DOMAIN,
|
||||
{
|
||||
automation.DOMAIN: [
|
||||
{
|
||||
"trigger": {
|
||||
"platform": "state",
|
||||
"entity_id": ["input_boolean.test_1", "input_boolean.test_1"],
|
||||
"from": "off",
|
||||
"to": "on",
|
||||
},
|
||||
"action": [
|
||||
{
|
||||
"repeat": {
|
||||
"count": 2,
|
||||
"sequence": [
|
||||
{
|
||||
"delay": {
|
||||
"hours": 0,
|
||||
"minutes": 0,
|
||||
"seconds": 0,
|
||||
"milliseconds": 100,
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
{"event": "repeat_test_script_finished", "event_data": {}},
|
||||
],
|
||||
"id": "automation_0",
|
||||
"mode": "restart",
|
||||
},
|
||||
]
|
||||
},
|
||||
)
|
||||
hass.states.async_set("input_boolean.test_1", "off")
|
||||
hass.states.async_set("input_boolean.test_2", "off")
|
||||
await hass.async_block_till_done()
|
||||
hass.states.async_set("input_boolean.test_1", "on")
|
||||
hass.states.async_set("input_boolean.test_2", "on")
|
||||
await asyncio.sleep(0)
|
||||
hass.states.async_set("input_boolean.test_1", "off")
|
||||
hass.states.async_set("input_boolean.test_2", "off")
|
||||
await asyncio.sleep(0)
|
||||
hass.states.async_set("input_boolean.test_1", "on")
|
||||
hass.states.async_set("input_boolean.test_2", "on")
|
||||
await hass.async_block_till_done()
|
||||
assert len(events) == 1
|
||||
|
|
Loading…
Reference in New Issue