def call_from_config(hass: HomeAssistant, config: ConfigType, variables: Optional[Sequence] = None, context: Optional[Context] = None) -> None: """Call a script based on a config entry.""" Script(hass, cv.SCRIPT_SCHEMA(config)).run(variables, context)
async def test_multiple_runs_no_wait(hass, script_mode): """Test multiple runs with no wait in script.""" logger = logging.getLogger("TEST") calls = [] heard_event = asyncio.Event() async def async_simulate_long_service(service): """Simulate a service that takes a not insignificant time.""" fire = service.data.get("fire") listen = service.data.get("listen") service_done = asyncio.Event() @callback def service_done_cb(event): logger.debug("simulated service (%s:%s) done", fire, listen) service_done.set() calls.append(service) logger.debug("simulated service (%s:%s) started", fire, listen) unsub = hass.bus.async_listen(listen, service_done_cb) hass.bus.async_fire(fire) await service_done.wait() unsub() hass.services.async_register("test", "script", async_simulate_long_service) @callback def heard_event_cb(event): logger.debug("heard: %s", event) heard_event.set() sequence = cv.SCRIPT_SCHEMA([ { "service": "test.script", "data_template": { "fire": "{{ fire1 }}", "listen": "{{ listen1 }}" }, }, { "service": "test.script", "data_template": { "fire": "{{ fire2 }}", "listen": "{{ listen2 }}" }, }, ]) script_obj = script.Script(hass, sequence, script_mode=script_mode) # Start script twice in such a way that second run will be started while first run # is in the middle of the first service call. unsub = hass.bus.async_listen("1", heard_event_cb) logger.debug("starting 1st script") hass.async_create_task( script_obj.async_run({ "fire1": "1", "listen1": "2", "fire2": "3", "listen2": "4" })) await asyncio.wait_for(heard_event.wait(), 1) unsub() logger.debug("starting 2nd script") await script_obj.async_run({ "fire1": "2", "listen1": "3", "fire2": "4", "listen2": "4" }) await hass.async_block_till_done() assert len(calls) == 4
async def test_script_mode_queue(hass): """Test overlapping runs with script_mode='queue'.""" event = "test_event" events = async_capture_events(hass, event) sequence = cv.SCRIPT_SCHEMA([ { "event": event, "event_data": { "value": 1 } }, { "wait_template": "{{ states.switch.test.state == 'off' }}" }, { "event": event, "event_data": { "value": 2 } }, { "wait_template": "{{ states.switch.test.state == 'on' }}" }, ]) logger = logging.getLogger("TEST") script_obj = script.Script(hass, sequence, script_mode="queue", logger=logger) wait_started_flag = async_watch_for_action(script_obj, "wait") try: hass.states.async_set("switch.test", "on") hass.async_create_task(script_obj.async_run()) await asyncio.wait_for(wait_started_flag.wait(), 1) assert script_obj.is_running assert len(events) == 1 assert events[0].data["value"] == 1 # Start second run of script while first run is suspended in wait_template. # This second run should not start until the first run has finished. hass.async_create_task(script_obj.async_run()) await asyncio.sleep(0) assert script_obj.is_running assert len(events) == 1 wait_started_flag.clear() hass.states.async_set("switch.test", "off") await asyncio.wait_for(wait_started_flag.wait(), 1) assert script_obj.is_running assert len(events) == 2 assert events[1].data["value"] == 2 wait_started_flag.clear() hass.states.async_set("switch.test", "on") await asyncio.wait_for(wait_started_flag.wait(), 1) await asyncio.sleep(0) assert script_obj.is_running assert len(events) == 3 assert events[2].data["value"] == 1 except (AssertionError, asyncio.TimeoutError): await script_obj.async_stop() raise else: hass.states.async_set("switch.test", "off") await asyncio.sleep(0) hass.states.async_set("switch.test", "on") await hass.async_block_till_done() assert not script_obj.is_running assert len(events) == 4 assert events[3].data["value"] == 2
async def test_script_mode_2(hass, caplog, script_mode, messages, last_events): """Test overlapping runs with script_mode='restart'.""" event = "test_event" events = async_capture_events(hass, event) sequence = cv.SCRIPT_SCHEMA([ { "event": event, "event_data": { "value": 1 } }, { "wait_template": "{{ states.switch.test.state == 'off' }}" }, { "event": event, "event_data": { "value": 2 } }, ]) logger = logging.getLogger("TEST") script_obj = script.Script(hass, sequence, script_mode=script_mode, logger=logger) wait_started_flag = async_watch_for_action(script_obj, "wait") try: hass.states.async_set("switch.test", "on") hass.async_create_task(script_obj.async_run()) await asyncio.wait_for(wait_started_flag.wait(), 1) assert script_obj.is_running assert len(events) == 1 assert events[0].data["value"] == 1 # Start second run of script while first run is suspended in wait_template. # This should stop first run then start a new run. wait_started_flag.clear() hass.async_create_task(script_obj.async_run()) await asyncio.wait_for(wait_started_flag.wait(), 1) assert script_obj.is_running assert len(events) == 2 assert events[1].data["value"] == 1 assert all( any(rec.levelname == "INFO" and rec.name == "TEST" and message in rec.message for rec in caplog.records) for message in messages) except (AssertionError, asyncio.TimeoutError): await script_obj.async_stop() raise else: hass.states.async_set("switch.test", "off") await hass.async_block_till_done() assert not script_obj.is_running assert len(events) == 2 + len(last_events) for idx, value in enumerate(last_events, start=2): assert events[idx].data["value"] == value
async def test_repeat_nested(hass, variables, first_last, inside_x): """Test nested repeats.""" event = "test_event" events = async_capture_events(hass, event) sequence = cv.SCRIPT_SCHEMA( [ { "event": event, "event_data_template": { "repeat": "{{ None if repeat is not defined else repeat }}", "x": "{{ None if x is not defined else x }}", }, }, { "repeat": { "count": 2, "sequence": [ { "event": event, "event_data_template": { "first": "{{ repeat.first }}", "index": "{{ repeat.index }}", "last": "{{ repeat.last }}", "x": "{{ None if x is not defined else x }}", }, }, { "repeat": { "count": 2, "sequence": { "event": event, "event_data_template": { "first": "{{ repeat.first }}", "index": "{{ repeat.index }}", "last": "{{ repeat.last }}", "x": "{{ None if x is not defined else x }}", }, }, } }, { "event": event, "event_data_template": { "first": "{{ repeat.first }}", "index": "{{ repeat.index }}", "last": "{{ repeat.last }}", "x": "{{ None if x is not defined else x }}", }, }, ], } }, { "event": event, "event_data_template": { "repeat": "{{ None if repeat is not defined else repeat }}", "x": "{{ None if x is not defined else x }}", }, }, ] ) script_obj = script.Script(hass, sequence, "test script") with mock.patch( "homeassistant.helpers.condition._LOGGER.error", side_effect=AssertionError("Template Error"), ): await script_obj.async_run(variables) assert len(events) == 10 assert events[0].data == first_last assert events[-1].data == first_last for index, result in enumerate( ( ("True", "1", "False", inside_x), ("True", "1", "False", inside_x), ("False", "2", "True", inside_x), ("True", "1", "False", inside_x), ("False", "2", "True", inside_x), ("True", "1", "False", inside_x), ("False", "2", "True", inside_x), ("False", "2", "True", inside_x), ), 1, ): assert events[index].data == { "first": result[0], "index": result[1], "last": result[2], "x": result[3], }
async def test_repeat_conditional(hass, condition): """Test repeat action w/ while option.""" event = "test_event" events = async_capture_events(hass, event) count = 3 sequence = { "repeat": { "sequence": [ { "event": event, "event_data_template": { "first": "{{ repeat.first }}", "index": "{{ repeat.index }}", }, }, { "wait_template": "{{ is_state('sensor.test', 'next') }}" }, { "wait_template": "{{ not is_state('sensor.test', 'next') }}" }, ], } } if condition == "while": sequence["repeat"]["while"] = { "condition": "template", "value_template": "{{ not is_state('sensor.test', 'done') }}", } else: sequence["repeat"]["until"] = { "condition": "template", "value_template": "{{ is_state('sensor.test', 'done') }}", } script_obj = script.Script(hass, cv.SCRIPT_SCHEMA(sequence)) wait_started = async_watch_for_action(script_obj, "wait") hass.states.async_set("sensor.test", "1") hass.async_create_task(script_obj.async_run()) try: for index in range(2, count + 1): await asyncio.wait_for(wait_started.wait(), 1) wait_started.clear() hass.states.async_set("sensor.test", "next") await asyncio.wait_for(wait_started.wait(), 1) wait_started.clear() hass.states.async_set("sensor.test", index) await asyncio.wait_for(wait_started.wait(), 1) hass.states.async_set("sensor.test", "next") await asyncio.wait_for(wait_started.wait(), 1) wait_started.clear() hass.states.async_set("sensor.test", "done") await asyncio.wait_for(hass.async_block_till_done(), 1) except asyncio.TimeoutError: await script_obj.async_stop() raise assert len(events) == count for index, event in enumerate(events): assert event.data.get("first") == str(index == 0) assert event.data.get("index") == str(index + 1)