async def setup_script(hass, notify_q, now, source): """Initialize and load the given pyscript.""" scripts = [ "/hello.py", ] with patch( "custom_components.pyscript.os.path.isdir", return_value=True ), patch("custom_components.pyscript.glob.iglob", return_value=scripts), patch( "custom_components.pyscript.global_ctx.open", mock_open(read_data=source) ), patch( "custom_components.pyscript.trigger.dt_now", return_value=now ), patch( "homeassistant.config.load_yaml_config_file", return_value={} ), patch( "custom_components.pyscript.open", mock_open(read_data=source) ), patch( "custom_components.pyscript.watchdog_start", return_value=None), patch( "custom_components.pyscript.os.path.getmtime", return_value=1000 ), patch( "custom_components.pyscript.global_ctx.os.path.getmtime", return_value=1000), patch( "custom_components.pyscript.install_requirements", return_value=None, ): assert await async_setup_component(hass, "pyscript", {DOMAIN: {}}) # # I'm not sure how to run the mock all the time, so just force the dt_now() # trigger function to return the given list of times in now. # def return_next_time(): nonlocal now if isinstance(now, list): if len(now) > 1: return now.pop(0) return now[0] return now trigger.__dict__["dt_now"] = return_next_time if notify_q: async def state_changed(event): var_name = event.data["entity_id"] if var_name != "pyscript.done": return value = event.data["new_state"].state await notify_q.put(value) hass.bus.async_listen(EVENT_STATE_CHANGED, state_changed)
async def setup_script(hass, notify_q, now, source): """Initialize and load the given pyscript.""" scripts = [ "/some/config/dir/pyscripts/hello.py", ] with patch("custom_components.pyscript.os.path.isdir", return_value=True), patch( "custom_components.pyscript.glob.iglob", return_value=scripts), patch( "custom_components.pyscript.global_ctx.open", mock_open(read_data=source), create=True, ), patch("custom_components.pyscript.trigger.dt_now", return_value=now): assert await async_setup_component(hass, "pyscript", {DOMAIN: {}}) # # I'm not sure how to run the mock all the time, so just force the dt_now() # trigger function to return the fixed time, now. # trigger.__dict__["dt_now"] = lambda: now if notify_q: async def state_changed(event): var_name = event.data["entity_id"] if var_name != "pyscript.done": return value = event.data["new_state"].state await notify_q.put(value) hass.bus.async_listen(EVENT_STATE_CHANGED, state_changed)
async def setup_script(hass, now, source, no_connect=False): """Initialize and load the given pyscript.""" scripts = [ "/some/config/dir/pyscripts/hello.py", ] with patch( "custom_components.pyscript.os.path.isdir", return_value=True), patch( "custom_components.pyscript.glob.iglob", return_value=scripts), patch( "custom_components.pyscript.global_ctx.open", mock_open(read_data=source), create=True, ), patch( "custom_components.pyscript.trigger.dt_now", return_value=now), patch( "homeassistant.config.load_yaml_config_file", return_value={}), patch( "custom_components.pyscript.install_requirements", return_value=None, ): assert await async_setup_component(hass, "pyscript", {DOMAIN: {}}) # # I'm not sure how to run the mock all the time, so just force the dt_now() # trigger function to return the given list of times in now. # def return_next_time(): nonlocal now if isinstance(now, list): if len(now) > 1: return now.pop(0) return now[0] return now trigger.__dict__["dt_now"] = return_next_time kernel_state_var = "pyscript.jupyter_ports_1234" kernel_cfg = { "ip": "127.0.0.1", "key": SECRET_KEY.decode("utf-8"), "signature_scheme": "hmac-sha256", "state_var": kernel_state_var, } if no_connect: kernel_cfg["no_connect_timeout"] = 0.0 await hass.services.async_call("pyscript", "jupyter_kernel_start", kernel_cfg) while True: ports_state = hass.states.get(kernel_state_var) if ports_state is not None: break await asyncio.sleep(2e-3) port_nums = json.loads(ports_state.state) sock = {} if no_connect: return sock, port_nums for name in PORT_NAMES: kernel_reader, kernel_writer = await asyncio.open_connection( "127.0.0.1", port_nums[name]) sock[name] = ZmqSocket(kernel_reader, kernel_writer, "ROUTER") await sock[name].handshake() return sock, port_nums
async def test_reload(hass, caplog): """Test reload.""" notify_q = asyncio.Queue(0) now = dt(2020, 7, 1, 11, 59, 59, 999999) source0 = """ seq_num = 0 @time_trigger def func_startup_sync(): global seq_num seq_num += 1 log.info(f"func_startup_sync setting pyscript.done = {seq_num}") pyscript.done = seq_num @service @state_trigger("pyscript.f1var1 == '1'") def func9(var_name=None, value=None): global seq_num seq_num += 1 log.info(f"func9 var = {var_name}, value = {value}") pyscript.done = [seq_num, var_name, int(value)] """ source1 = """ seq_num = 10 @time_trigger("startup") def func_startup_sync(): global seq_num seq_num += 1 log.info(f"func_startup_sync setting pyscript.done = {seq_num}") pyscript.done = seq_num @service @state_trigger("pyscript.f5var1 == '1'") def func5(var_name=None, value=None): global seq_num seq_num += 1 log.info(f"func5 var = {var_name}, value = {value}") pyscript.done = [seq_num, var_name, int(value)] """ await setup_script(hass, notify_q, now, source0) # # run and reload 6 times with different source files to make sure seqNum # gets reset, autostart of func_startup_sync happens and triggers work each time # # first time: fire event to startup triggers and run func_startup_sync # hass.bus.async_fire(EVENT_HOMEASSISTANT_STARTED) for i in range(6): if i & 1: seq_num = 10 assert not hass.services.has_service("pyscript", "func9") assert hass.services.has_service("pyscript", "reload") assert hass.services.has_service("pyscript", "func5") seq_num += 1 assert literal_eval(await wait_until_done(notify_q)) == seq_num seq_num += 1 # initialize the trigger and active variables hass.states.async_set("pyscript.f5var1", 0) # try some values that shouldn't work, then one that does hass.states.async_set("pyscript.f5var1", "string") hass.states.async_set("pyscript.f5var1", 1) assert literal_eval(await wait_until_done(notify_q)) == [ seq_num, "pyscript.f5var1", 1, ] assert "func5 var = pyscript.f5var1, value = 1" in caplog.text next_source = source0 else: seq_num = 0 assert hass.services.has_service("pyscript", "func9") assert hass.services.has_service("pyscript", "reload") assert not hass.services.has_service("pyscript", "func5") seq_num += 1 assert literal_eval(await wait_until_done(notify_q)) == seq_num seq_num += 1 # initialize the trigger and active variables hass.states.async_set("pyscript.f1var1", 0) # try some values that shouldn't work, then one that does hass.states.async_set("pyscript.f1var1", "string") hass.states.async_set("pyscript.f1var1", 1) assert literal_eval(await wait_until_done(notify_q)) == [ seq_num, "pyscript.f1var1", 1, ] assert "func9 var = pyscript.f1var1, value = 1" in caplog.text next_source = source1 # # now reload the other source file # scripts = [ "/hello.py", ] with patch( "custom_components.pyscript.os.path.isdir", return_value=True ), patch( "custom_components.pyscript.glob.iglob", return_value=scripts ), patch("custom_components.pyscript.global_ctx.open", mock_open(read_data=next_source)), patch( "custom_components.pyscript.open", mock_open(read_data=next_source) ), patch( "custom_components.pyscript.trigger.dt_now", return_value=now ), patch( "homeassistant.config.load_yaml_config_file", return_value={} ), patch( "custom_components.pyscript.os.path.getmtime", return_value=1000 ), patch( "custom_components.pyscript.global_ctx.os.path.getmtime", return_value=1000), patch( "custom_components.pyscript.install_requirements", return_value=None, ): reload_param = {} if i % 2 == 1: # # on alternate times, just reload the specific file we are testing with # reload_param = {"global_ctx": "file.hello"} await hass.services.async_call("pyscript", "reload", reload_param, blocking=True) if i % 3 == 0: # # reload a file that doesn't exist; will log error and do nothing # await hass.services.async_call( "pyscript", "reload", {"global_ctx": "file.nosuchfile"}, blocking=True) assert "pyscript.reload: no global context 'file.nosuchfile' to reload" in caplog.text