mirror of
https://github.com/home-assistant/core.git
synced 2025-07-21 20:27:08 +00:00
Add missing timeout to command_line platforms: cover, notify, switch (#38497)
* Add missing timeout to command_line platforms: cover, notify, switch * add timeout test for notify
This commit is contained in:
parent
dddcb8e299
commit
7b728b17f7
@ -1 +1,42 @@
|
|||||||
"""The command_line component."""
|
"""The command_line component."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def call_shell_with_timeout(command, timeout):
|
||||||
|
"""Run a shell command with a timeout."""
|
||||||
|
try:
|
||||||
|
_LOGGER.debug("Running command: %s", command)
|
||||||
|
subprocess.check_output(
|
||||||
|
command, shell=True, timeout=timeout # nosec # shell by design
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
except subprocess.CalledProcessError as proc_exception:
|
||||||
|
_LOGGER.error("Command failed: %s", command)
|
||||||
|
return proc_exception.returncode
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
_LOGGER.error("Timeout for command: %s", command)
|
||||||
|
return -1
|
||||||
|
except subprocess.SubprocessError:
|
||||||
|
_LOGGER.error("Error trying to exec command: %s", command)
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
def check_output_or_log(command, timeout):
|
||||||
|
"""Run a shell command with a timeout and return the output."""
|
||||||
|
try:
|
||||||
|
return_value = subprocess.check_output(
|
||||||
|
command, shell=True, timeout=timeout # nosec # shell by design
|
||||||
|
)
|
||||||
|
return return_value.strip().decode("utf-8")
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
_LOGGER.error("Command failed: %s", command)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
_LOGGER.error("Timeout for command: %s", command)
|
||||||
|
except subprocess.SubprocessError:
|
||||||
|
_LOGGER.error("Error trying to exec command: %s", command)
|
||||||
|
|
||||||
|
return None
|
||||||
|
@ -19,6 +19,7 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT
|
||||||
from .sensor import CommandSensorData
|
from .sensor import CommandSensorData
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@ -29,8 +30,6 @@ DEFAULT_PAYLOAD_OFF = "OFF"
|
|||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=60)
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
CONF_COMMAND_TIMEOUT = "command_timeout"
|
|
||||||
DEFAULT_TIMEOUT = 15
|
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
|
4
homeassistant/components/command_line/const.py
Normal file
4
homeassistant/components/command_line/const.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
"""Allows to configure custom shell commands to turn a value for a sensor."""
|
||||||
|
|
||||||
|
CONF_COMMAND_TIMEOUT = "command_timeout"
|
||||||
|
DEFAULT_TIMEOUT = 15
|
@ -1,6 +1,5 @@
|
|||||||
"""Support for command line covers."""
|
"""Support for command line covers."""
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -16,6 +15,9 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from . import call_shell_with_timeout, check_output_or_log
|
||||||
|
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
COVER_SCHEMA = vol.Schema(
|
COVER_SCHEMA = vol.Schema(
|
||||||
@ -26,6 +28,7 @@ COVER_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(CONF_COMMAND_STOP, default="true"): cv.string,
|
vol.Optional(CONF_COMMAND_STOP, default="true"): cv.string,
|
||||||
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
||||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -48,11 +51,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
CommandCover(
|
CommandCover(
|
||||||
hass,
|
hass,
|
||||||
device_config.get(CONF_FRIENDLY_NAME, device_name),
|
device_config.get(CONF_FRIENDLY_NAME, device_name),
|
||||||
device_config.get(CONF_COMMAND_OPEN),
|
device_config[CONF_COMMAND_OPEN],
|
||||||
device_config.get(CONF_COMMAND_CLOSE),
|
device_config[CONF_COMMAND_CLOSE],
|
||||||
device_config.get(CONF_COMMAND_STOP),
|
device_config[CONF_COMMAND_STOP],
|
||||||
device_config.get(CONF_COMMAND_STATE),
|
device_config.get(CONF_COMMAND_STATE),
|
||||||
value_template,
|
value_template,
|
||||||
|
device_config[CONF_COMMAND_TIMEOUT],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -75,6 +79,7 @@ class CommandCover(CoverEntity):
|
|||||||
command_stop,
|
command_stop,
|
||||||
command_state,
|
command_state,
|
||||||
value_template,
|
value_template,
|
||||||
|
timeout,
|
||||||
):
|
):
|
||||||
"""Initialize the cover."""
|
"""Initialize the cover."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
@ -85,31 +90,23 @@ class CommandCover(CoverEntity):
|
|||||||
self._command_stop = command_stop
|
self._command_stop = command_stop
|
||||||
self._command_state = command_state
|
self._command_state = command_state
|
||||||
self._value_template = value_template
|
self._value_template = value_template
|
||||||
|
self._timeout = timeout
|
||||||
|
|
||||||
@staticmethod
|
def _move_cover(self, command):
|
||||||
def _move_cover(command):
|
|
||||||
"""Execute the actual commands."""
|
"""Execute the actual commands."""
|
||||||
_LOGGER.info("Running command: %s", command)
|
_LOGGER.info("Running command: %s", command)
|
||||||
|
|
||||||
success = subprocess.call(command, shell=True) == 0 # nosec # shell by design
|
success = call_shell_with_timeout(command, self._timeout) == 0
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
_LOGGER.error("Command failed: %s", command)
|
_LOGGER.error("Command failed: %s", command)
|
||||||
|
|
||||||
return success
|
return success
|
||||||
|
|
||||||
@staticmethod
|
def _query_state_value(self, command):
|
||||||
def _query_state_value(command):
|
|
||||||
"""Execute state command for return value."""
|
"""Execute state command for return value."""
|
||||||
_LOGGER.info("Running state command: %s", command)
|
_LOGGER.info("Running state value command: %s", command)
|
||||||
|
return check_output_or_log(command, self._timeout)
|
||||||
try:
|
|
||||||
return_value = subprocess.check_output(
|
|
||||||
command, shell=True # nosec # shell by design
|
|
||||||
)
|
|
||||||
return return_value.strip().decode("utf-8")
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
_LOGGER.error("Command failed: %s", command)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
|
@ -8,26 +8,34 @@ from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationSer
|
|||||||
from homeassistant.const import CONF_COMMAND, CONF_NAME
|
from homeassistant.const import CONF_COMMAND, CONF_NAME
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
||||||
{vol.Required(CONF_COMMAND): cv.string, vol.Optional(CONF_NAME): cv.string}
|
{
|
||||||
|
vol.Required(CONF_COMMAND): cv.string,
|
||||||
|
vol.Optional(CONF_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_service(hass, config, discovery_info=None):
|
def get_service(hass, config, discovery_info=None):
|
||||||
"""Get the Command Line notification service."""
|
"""Get the Command Line notification service."""
|
||||||
command = config[CONF_COMMAND]
|
command = config[CONF_COMMAND]
|
||||||
|
timeout = config[CONF_COMMAND_TIMEOUT]
|
||||||
|
|
||||||
return CommandLineNotificationService(command)
|
return CommandLineNotificationService(command, timeout)
|
||||||
|
|
||||||
|
|
||||||
class CommandLineNotificationService(BaseNotificationService):
|
class CommandLineNotificationService(BaseNotificationService):
|
||||||
"""Implement the notification service for the Command Line service."""
|
"""Implement the notification service for the Command Line service."""
|
||||||
|
|
||||||
def __init__(self, command):
|
def __init__(self, command, timeout):
|
||||||
"""Initialize the service."""
|
"""Initialize the service."""
|
||||||
self.command = command
|
self.command = command
|
||||||
|
self._timeout = timeout
|
||||||
|
|
||||||
def send_message(self, message="", **kwargs):
|
def send_message(self, message="", **kwargs):
|
||||||
"""Send a message to a command line."""
|
"""Send a message to a command line."""
|
||||||
@ -38,8 +46,10 @@ class CommandLineNotificationService(BaseNotificationService):
|
|||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
shell=True, # nosec # shell by design
|
shell=True, # nosec # shell by design
|
||||||
)
|
)
|
||||||
proc.communicate(input=message)
|
proc.communicate(input=message, timeout=self._timeout)
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
_LOGGER.error("Command failed: %s", self.command)
|
_LOGGER.error("Command failed: %s", self.command)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
_LOGGER.error("Timeout for command: %s", self.command)
|
||||||
except subprocess.SubprocessError:
|
except subprocess.SubprocessError:
|
||||||
_LOGGER.error("Error trying to exec Command: %s", self.command)
|
_LOGGER.error("Error trying to exec command: %s", self.command)
|
||||||
|
@ -3,7 +3,6 @@ from collections.abc import Mapping
|
|||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -20,13 +19,14 @@ from homeassistant.helpers import template
|
|||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.entity import Entity
|
from homeassistant.helpers.entity import Entity
|
||||||
|
|
||||||
|
from . import check_output_or_log
|
||||||
|
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
CONF_COMMAND_TIMEOUT = "command_timeout"
|
|
||||||
CONF_JSON_ATTRIBUTES = "json_attributes"
|
CONF_JSON_ATTRIBUTES = "json_attributes"
|
||||||
|
|
||||||
DEFAULT_NAME = "Command Sensor"
|
DEFAULT_NAME = "Command Sensor"
|
||||||
DEFAULT_TIMEOUT = 15
|
|
||||||
|
|
||||||
SCAN_INTERVAL = timedelta(seconds=60)
|
SCAN_INTERVAL = timedelta(seconds=60)
|
||||||
|
|
||||||
@ -171,13 +171,6 @@ class CommandSensorData:
|
|||||||
else:
|
else:
|
||||||
# Template used. Construct the string used in the shell
|
# Template used. Construct the string used in the shell
|
||||||
command = f"{prog} {rendered_args}"
|
command = f"{prog} {rendered_args}"
|
||||||
try:
|
|
||||||
_LOGGER.debug("Running command: %s", command)
|
_LOGGER.debug("Running command: %s", command)
|
||||||
return_value = subprocess.check_output(
|
self.value = check_output_or_log(command, self.timeout)
|
||||||
command, shell=True, timeout=self.timeout # nosec # shell by design
|
|
||||||
)
|
|
||||||
self.value = return_value.strip().decode("utf-8")
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
_LOGGER.error("Command failed: %s", command)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
_LOGGER.error("Timeout for command: %s", command)
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""Support for custom shell commands to turn a switch on/off."""
|
"""Support for custom shell commands to turn a switch on/off."""
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
@ -19,6 +18,9 @@ from homeassistant.const import (
|
|||||||
)
|
)
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from . import call_shell_with_timeout, check_output_or_log
|
||||||
|
from .const import CONF_COMMAND_TIMEOUT, DEFAULT_TIMEOUT
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
SWITCH_SCHEMA = vol.Schema(
|
SWITCH_SCHEMA = vol.Schema(
|
||||||
@ -28,6 +30,7 @@ SWITCH_SCHEMA = vol.Schema(
|
|||||||
vol.Optional(CONF_COMMAND_STATE): cv.string,
|
vol.Optional(CONF_COMMAND_STATE): cv.string,
|
||||||
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
vol.Optional(CONF_FRIENDLY_NAME): cv.string,
|
||||||
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
|
||||||
|
vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,10 +55,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
|||||||
hass,
|
hass,
|
||||||
object_id,
|
object_id,
|
||||||
device_config.get(CONF_FRIENDLY_NAME, object_id),
|
device_config.get(CONF_FRIENDLY_NAME, object_id),
|
||||||
device_config.get(CONF_COMMAND_ON),
|
device_config[CONF_COMMAND_ON],
|
||||||
device_config.get(CONF_COMMAND_OFF),
|
device_config[CONF_COMMAND_OFF],
|
||||||
device_config.get(CONF_COMMAND_STATE),
|
device_config.get(CONF_COMMAND_STATE),
|
||||||
value_template,
|
value_template,
|
||||||
|
device_config[CONF_COMMAND_TIMEOUT],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -78,6 +82,7 @@ class CommandSwitch(SwitchEntity):
|
|||||||
command_off,
|
command_off,
|
||||||
command_state,
|
command_state,
|
||||||
value_template,
|
value_template,
|
||||||
|
timeout,
|
||||||
):
|
):
|
||||||
"""Initialize the switch."""
|
"""Initialize the switch."""
|
||||||
self._hass = hass
|
self._hass = hass
|
||||||
@ -88,37 +93,28 @@ class CommandSwitch(SwitchEntity):
|
|||||||
self._command_off = command_off
|
self._command_off = command_off
|
||||||
self._command_state = command_state
|
self._command_state = command_state
|
||||||
self._value_template = value_template
|
self._value_template = value_template
|
||||||
|
self._timeout = timeout
|
||||||
|
|
||||||
@staticmethod
|
def _switch(self, command):
|
||||||
def _switch(command):
|
|
||||||
"""Execute the actual commands."""
|
"""Execute the actual commands."""
|
||||||
_LOGGER.info("Running command: %s", command)
|
_LOGGER.info("Running command: %s", command)
|
||||||
|
|
||||||
success = subprocess.call(command, shell=True) == 0 # nosec # shell by design
|
success = call_shell_with_timeout(command, self._timeout) == 0
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
_LOGGER.error("Command failed: %s", command)
|
_LOGGER.error("Command failed: %s", command)
|
||||||
|
|
||||||
return success
|
return success
|
||||||
|
|
||||||
@staticmethod
|
def _query_state_value(self, command):
|
||||||
def _query_state_value(command):
|
|
||||||
"""Execute state command for return value."""
|
"""Execute state command for return value."""
|
||||||
_LOGGER.info("Running state command: %s", command)
|
_LOGGER.info("Running state value command: %s", command)
|
||||||
|
return check_output_or_log(command, self._timeout)
|
||||||
|
|
||||||
try:
|
def _query_state_code(self, command):
|
||||||
return_value = subprocess.check_output(
|
|
||||||
command, shell=True # nosec # shell by design
|
|
||||||
)
|
|
||||||
return return_value.strip().decode("utf-8")
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
_LOGGER.error("Command failed: %s", command)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _query_state_code(command):
|
|
||||||
"""Execute state command for return code."""
|
"""Execute state command for return code."""
|
||||||
_LOGGER.info("Running state command: %s", command)
|
_LOGGER.info("Running state code command: %s", command)
|
||||||
return subprocess.call(command, shell=True) == 0 # nosec # shell by design
|
return call_shell_with_timeout(command, self._timeout) == 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def should_poll(self):
|
def should_poll(self):
|
||||||
@ -146,8 +142,8 @@ class CommandSwitch(SwitchEntity):
|
|||||||
_LOGGER.error("No state command specified")
|
_LOGGER.error("No state command specified")
|
||||||
return
|
return
|
||||||
if self._value_template:
|
if self._value_template:
|
||||||
return CommandSwitch._query_state_value(self._command_state)
|
return self._query_state_value(self._command_state)
|
||||||
return CommandSwitch._query_state_code(self._command_state)
|
return self._query_state_code(self._command_state)
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
"""Update device state."""
|
"""Update device state."""
|
||||||
@ -159,12 +155,12 @@ class CommandSwitch(SwitchEntity):
|
|||||||
|
|
||||||
def turn_on(self, **kwargs):
|
def turn_on(self, **kwargs):
|
||||||
"""Turn the device on."""
|
"""Turn the device on."""
|
||||||
if CommandSwitch._switch(self._command_on) and not self._command_state:
|
if self._switch(self._command_on) and not self._command_state:
|
||||||
self._state = True
|
self._state = True
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
|
||||||
def turn_off(self, **kwargs):
|
def turn_off(self, **kwargs):
|
||||||
"""Turn the device off."""
|
"""Turn the device off."""
|
||||||
if CommandSwitch._switch(self._command_off) and not self._command_state:
|
if self._switch(self._command_off) and not self._command_state:
|
||||||
self._state = False
|
self._state = False
|
||||||
self.schedule_update_ha_state()
|
self.schedule_update_ha_state()
|
||||||
|
@ -27,6 +27,7 @@ def rs(hass):
|
|||||||
"command_stop",
|
"command_stop",
|
||||||
"command_state",
|
"command_state",
|
||||||
None,
|
None,
|
||||||
|
15,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ def test_query_state_value(rs):
|
|||||||
assert "foo bar" == result
|
assert "foo bar" == result
|
||||||
assert mock_run.call_count == 1
|
assert mock_run.call_count == 1
|
||||||
assert mock_run.call_args == mock.call(
|
assert mock_run.call_args == mock.call(
|
||||||
"runme", shell=True, # nosec # shell by design
|
"runme", shell=True, timeout=15 # nosec # shell by design
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import tempfile
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import homeassistant.components.notify as notify
|
import homeassistant.components.notify as notify
|
||||||
from homeassistant.setup import setup_component
|
from homeassistant.setup import async_setup_component, setup_component
|
||||||
|
|
||||||
from tests.async_mock import patch
|
from tests.async_mock import patch
|
||||||
from tests.common import assert_setup_component, get_test_home_assistant
|
from tests.common import assert_setup_component, get_test_home_assistant
|
||||||
@ -93,3 +93,25 @@ class TestCommandLine(unittest.TestCase):
|
|||||||
"notify", "test", {"message": "error"}, blocking=True
|
"notify", "test", {"message": "error"}, blocking=True
|
||||||
)
|
)
|
||||||
assert mock_error.call_count == 1
|
assert mock_error.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_timeout(hass, caplog):
|
||||||
|
"""Test we do not block forever."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
notify.DOMAIN,
|
||||||
|
{
|
||||||
|
"notify": {
|
||||||
|
"name": "test",
|
||||||
|
"platform": "command_line",
|
||||||
|
"command": "sleep 10000",
|
||||||
|
"command_timeout": 0.0000001,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert await hass.services.async_call(
|
||||||
|
"notify", "test", {"message": "error"}, blocking=True
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert "Timeout" in caplog.text
|
||||||
|
@ -74,7 +74,7 @@ class TestCommandSensorSensor(unittest.TestCase):
|
|||||||
"""Ensure command with templates and quotes get rendered properly."""
|
"""Ensure command with templates and quotes get rendered properly."""
|
||||||
self.hass.states.set("sensor.test_state", "Works 2")
|
self.hass.states.set("sensor.test_state", "Works 2")
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.command_line.sensor.subprocess.check_output",
|
"homeassistant.components.command_line.subprocess.check_output",
|
||||||
return_value=b"Works\n",
|
return_value=b"Works\n",
|
||||||
) as check_output:
|
) as check_output:
|
||||||
data = command_line.CommandSensorData(
|
data = command_line.CommandSensorData(
|
||||||
|
@ -180,13 +180,14 @@ class TestCommandSwitch(unittest.TestCase):
|
|||||||
"echo 'off command'",
|
"echo 'off command'",
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
15,
|
||||||
]
|
]
|
||||||
|
|
||||||
no_state_device = command_line.CommandSwitch(*init_args)
|
no_state_device = command_line.CommandSwitch(*init_args)
|
||||||
assert no_state_device.assumed_state
|
assert no_state_device.assumed_state
|
||||||
|
|
||||||
# Set state command
|
# Set state command
|
||||||
init_args[-2] = "cat {}"
|
init_args[-3] = "cat {}"
|
||||||
|
|
||||||
state_device = command_line.CommandSwitch(*init_args)
|
state_device = command_line.CommandSwitch(*init_args)
|
||||||
assert not state_device.assumed_state
|
assert not state_device.assumed_state
|
||||||
@ -201,6 +202,7 @@ class TestCommandSwitch(unittest.TestCase):
|
|||||||
"echo 'off command'",
|
"echo 'off command'",
|
||||||
False,
|
False,
|
||||||
None,
|
None,
|
||||||
|
15,
|
||||||
]
|
]
|
||||||
|
|
||||||
test_switch = command_line.CommandSwitch(*init_args)
|
test_switch = command_line.CommandSwitch(*init_args)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user