Convert command_line to use asyncio for subprocesses (#111927)
* Convert command_line to use asyncio for subprocesses * fixes * fix * fixes * more test fixes * more fixes * fixes * preen
This commit is contained in:
parent
5f65315e86
commit
c0f7ade92b
10 changed files with 115 additions and 117 deletions
|
@ -1 +1,30 @@
|
|||
"""Tests for command_line component."""
|
||||
|
||||
import asyncio
|
||||
from contextlib import contextmanager
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
|
||||
@contextmanager
|
||||
def mock_asyncio_subprocess_run(
|
||||
response: bytes = b"", returncode: int = 0, exception: Exception = None
|
||||
):
|
||||
"""Mock create_subprocess_shell."""
|
||||
|
||||
class MockProcess(asyncio.subprocess.Process):
|
||||
@property
|
||||
def returncode(self):
|
||||
return returncode
|
||||
|
||||
async def communicate(self):
|
||||
if exception:
|
||||
raise exception
|
||||
return response, b""
|
||||
|
||||
mock_process = MockProcess(MagicMock(), MagicMock(), MagicMock())
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.asyncio.create_subprocess_shell",
|
||||
return_value=mock_process,
|
||||
) as mock:
|
||||
yield mock
|
||||
|
|
|
@ -21,6 +21,8 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import mock_asyncio_subprocess_run
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
|
||||
|
@ -329,10 +331,7 @@ async def test_availability(
|
|||
|
||||
hass.states.async_set("sensor.input1", "off")
|
||||
await hass.async_block_till_done()
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
return_value=b"0",
|
||||
):
|
||||
with mock_asyncio_subprocess_run(b"0"):
|
||||
freezer.tick(timedelta(minutes=1))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
|
|
@ -30,16 +30,15 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers import entity_registry as er
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from . import mock_asyncio_subprocess_run
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
|
||||
async def test_no_poll_when_cover_has_no_command_state(hass: HomeAssistant) -> None:
|
||||
"""Test that the cover does not polls when there's no state command."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
return_value=b"50\n",
|
||||
) as check_output:
|
||||
with mock_asyncio_subprocess_run(b"50\n") as mock_subprocess_run:
|
||||
assert await setup.async_setup_component(
|
||||
hass,
|
||||
COVER_DOMAIN,
|
||||
|
@ -51,7 +50,7 @@ async def test_no_poll_when_cover_has_no_command_state(hass: HomeAssistant) -> N
|
|||
)
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
assert not check_output.called
|
||||
assert not mock_subprocess_run.called
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -74,17 +73,13 @@ async def test_poll_when_cover_has_command_state(
|
|||
) -> None:
|
||||
"""Test that the cover polls when there's a state command."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
return_value=b"50\n",
|
||||
) as check_output:
|
||||
with mock_asyncio_subprocess_run(b"50\n") as mock_subprocess_run:
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
check_output.assert_called_once_with(
|
||||
mock_subprocess_run.assert_called_once_with(
|
||||
"echo state",
|
||||
shell=True, # noqa: S604 # shell by design
|
||||
timeout=15,
|
||||
close_fds=False,
|
||||
stdout=-1,
|
||||
)
|
||||
|
||||
|
||||
|
@ -379,10 +374,7 @@ async def test_availability(
|
|||
|
||||
hass.states.async_set("sensor.input1", "off")
|
||||
await hass.async_block_till_done()
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
return_value=b"50\n",
|
||||
):
|
||||
with mock_asyncio_subprocess_run(b"50\n"):
|
||||
freezer.tick(timedelta(minutes=1))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import annotations
|
|||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import subprocess
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
|
@ -22,6 +21,8 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import mock_asyncio_subprocess_run
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
|
||||
|
@ -132,10 +133,7 @@ async def test_template_render_with_quote(hass: HomeAssistant) -> None:
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
return_value=b"Works\n",
|
||||
) as check_output:
|
||||
with mock_asyncio_subprocess_run(b"Works\n") as mock_subprocess_run:
|
||||
# Give time for template to load
|
||||
async_fire_time_changed(
|
||||
hass,
|
||||
|
@ -143,11 +141,10 @@ async def test_template_render_with_quote(hass: HomeAssistant) -> None:
|
|||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(check_output.mock_calls) == 1
|
||||
check_output.assert_called_with(
|
||||
assert len(mock_subprocess_run.mock_calls) == 1
|
||||
mock_subprocess_run.assert_called_with(
|
||||
'echo "sensor_value" "3 4"',
|
||||
shell=True, # noqa: S604 # shell by design
|
||||
timeout=15,
|
||||
stdout=-1,
|
||||
close_fds=False,
|
||||
)
|
||||
|
||||
|
@ -679,10 +676,7 @@ async def test_template_not_error_when_data_is_none(
|
|||
) -> None:
|
||||
"""Test command sensor with template not logging error when data is None."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
side_effect=subprocess.CalledProcessError,
|
||||
):
|
||||
with mock_asyncio_subprocess_run(returncode=1):
|
||||
await setup.async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
|
@ -747,10 +741,7 @@ async def test_availability(
|
|||
|
||||
hass.states.async_set("sensor.input1", "off")
|
||||
await hass.async_block_till_done()
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
return_value=b"January 17, 2022",
|
||||
):
|
||||
with mock_asyncio_subprocess_run(b"January 17, 2022"):
|
||||
freezer.tick(timedelta(minutes=1))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
|
|
@ -5,7 +5,6 @@ import asyncio
|
|||
from datetime import timedelta
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
from unittest.mock import patch
|
||||
|
||||
|
@ -32,6 +31,8 @@ from homeassistant.core import HomeAssistant
|
|||
from homeassistant.helpers import entity_registry as er
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
from . import mock_asyncio_subprocess_run
|
||||
|
||||
from tests.common import async_fire_time_changed
|
||||
|
||||
|
||||
|
@ -374,13 +375,7 @@ async def test_switch_command_state_code_exceptions(
|
|||
) -> None:
|
||||
"""Test that switch state code exceptions are handled correctly."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
side_effect=[
|
||||
subprocess.TimeoutExpired("cmd", 10),
|
||||
subprocess.SubprocessError(),
|
||||
],
|
||||
) as check_output:
|
||||
with mock_asyncio_subprocess_run(exception=asyncio.TimeoutError) as run:
|
||||
await setup.async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
|
@ -401,12 +396,13 @@ async def test_switch_command_state_code_exceptions(
|
|||
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
assert check_output.called
|
||||
assert run.called
|
||||
assert "Timeout for command" in caplog.text
|
||||
|
||||
with mock_asyncio_subprocess_run(returncode=127) as run:
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 2)
|
||||
await hass.async_block_till_done()
|
||||
assert check_output.called
|
||||
assert run.called
|
||||
assert "Error trying to exec command" in caplog.text
|
||||
|
||||
|
||||
|
@ -415,13 +411,7 @@ async def test_switch_command_state_value_exceptions(
|
|||
) -> None:
|
||||
"""Test that switch state value exceptions are handled correctly."""
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
side_effect=[
|
||||
subprocess.TimeoutExpired("cmd", 10),
|
||||
subprocess.SubprocessError(),
|
||||
],
|
||||
) as check_output:
|
||||
with mock_asyncio_subprocess_run(exception=asyncio.TimeoutError) as run:
|
||||
await setup.async_setup_component(
|
||||
hass,
|
||||
DOMAIN,
|
||||
|
@ -443,13 +433,14 @@ async def test_switch_command_state_value_exceptions(
|
|||
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL)
|
||||
await hass.async_block_till_done()
|
||||
assert check_output.call_count == 1
|
||||
assert run.call_count == 1
|
||||
assert "Timeout for command" in caplog.text
|
||||
|
||||
with mock_asyncio_subprocess_run(returncode=127) as run:
|
||||
async_fire_time_changed(hass, dt_util.utcnow() + SCAN_INTERVAL * 2)
|
||||
await hass.async_block_till_done()
|
||||
assert check_output.call_count == 2
|
||||
assert "Error trying to exec command" in caplog.text
|
||||
assert run.call_count == 1
|
||||
assert "Command failed (with return code 127)" in caplog.text
|
||||
|
||||
|
||||
async def test_unique_id(
|
||||
|
@ -750,10 +741,7 @@ async def test_availability(
|
|||
|
||||
hass.states.async_set("sensor.input1", "off")
|
||||
await hass.async_block_till_done()
|
||||
with patch(
|
||||
"homeassistant.components.command_line.utils.subprocess.check_output",
|
||||
return_value=b"50\n",
|
||||
):
|
||||
with mock_asyncio_subprocess_run(b"50\n"):
|
||||
freezer.tick(timedelta(minutes=1))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue