mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-23 00:56:29 +00:00
Make dbus-fast calls more robust (#4005)
* Make dbus-fast calls more robust * Handle all exceptions and add test * DBus minimal can't return commands list Co-authored-by: Mike Degatano <michael.degatano@gmail.com>
This commit is contained in:
parent
348fb56cb5
commit
2809f23391
@ -30,6 +30,7 @@ from ..exceptions import (
|
||||
DBusTimeoutError,
|
||||
HassioNotSupportedError,
|
||||
)
|
||||
from .sentry import capture_exception
|
||||
|
||||
_LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@ -103,6 +104,9 @@ class DBus:
|
||||
return await getattr(proxy_interface, method)(*args)
|
||||
except DBusError as err:
|
||||
raise DBus.from_dbus_error(err)
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
capture_exception(err)
|
||||
raise DBusFatalError(str(err)) from err
|
||||
|
||||
def _add_interfaces(self):
|
||||
"""Make proxy interfaces out of introspection data."""
|
||||
|
@ -129,8 +129,41 @@ def mock_get_properties(object_path: str, interface: str) -> str:
|
||||
return load_json_fixture(f"{fixture}.json")
|
||||
|
||||
|
||||
async def mock_init_proxy(self):
|
||||
"""Mock init dbus proxy."""
|
||||
filetype = "xml"
|
||||
fixture = (
|
||||
self.object_path.replace("/", "_")[1:]
|
||||
if self.object_path != DBUS_OBJECT_BASE
|
||||
else self.bus_name.replace(".", "_")
|
||||
)
|
||||
|
||||
if not exists_fixture(f"{fixture}.{filetype}"):
|
||||
fixture = re.sub(r"_[0-9]+$", "", fixture)
|
||||
|
||||
# special case
|
||||
if exists_fixture(f"{fixture}_~.{filetype}"):
|
||||
fixture = f"{fixture}_~"
|
||||
|
||||
# Use dbus-next infrastructure to parse introspection xml
|
||||
self._proxy_obj = ProxyObject(
|
||||
self.bus_name,
|
||||
self.object_path,
|
||||
load_fixture(f"{fixture}.{filetype}"),
|
||||
self._bus,
|
||||
)
|
||||
self._add_interfaces()
|
||||
|
||||
if DBUS_INTERFACE_PROPERTIES in self._proxies:
|
||||
setattr(
|
||||
self._proxies[DBUS_INTERFACE_PROPERTIES],
|
||||
"call_get_all",
|
||||
lambda interface: mock_get_properties(self.object_path, interface),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dbus(dbus_bus: MessageBus) -> DBus:
|
||||
def dbus(dbus_bus: MessageBus) -> list[str]:
|
||||
"""Mock DBUS."""
|
||||
dbus_commands = []
|
||||
|
||||
@ -150,31 +183,6 @@ def dbus(dbus_bus: MessageBus) -> DBus:
|
||||
async def mock_signal___aexit__(self, exc_t, exc_v, exc_tb):
|
||||
pass
|
||||
|
||||
async def mock_init_proxy(self):
|
||||
|
||||
filetype = "xml"
|
||||
fixture = (
|
||||
self.object_path.replace("/", "_")[1:]
|
||||
if self.object_path != DBUS_OBJECT_BASE
|
||||
else self.bus_name.replace(".", "_")
|
||||
)
|
||||
|
||||
if not exists_fixture(f"{fixture}.{filetype}"):
|
||||
fixture = re.sub(r"_[0-9]+$", "", fixture)
|
||||
|
||||
# special case
|
||||
if exists_fixture(f"{fixture}_~.{filetype}"):
|
||||
fixture = f"{fixture}_~"
|
||||
|
||||
# Use dbus-next infrastructure to parse introspection xml
|
||||
self._proxy_obj = ProxyObject(
|
||||
self.bus_name,
|
||||
self.object_path,
|
||||
load_fixture(f"{fixture}.{filetype}"),
|
||||
dbus_bus,
|
||||
)
|
||||
self._add_interfaces()
|
||||
|
||||
async def mock_call_dbus(
|
||||
proxy_interface: ProxyInterface,
|
||||
method: str,
|
||||
@ -235,6 +243,15 @@ def dbus(dbus_bus: MessageBus) -> DBus:
|
||||
yield dbus_commands
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def dbus_minimal(dbus_bus: MessageBus) -> MessageBus:
|
||||
"""Mock DBus without mocking call_dbus or signals but handle properties fixture."""
|
||||
with patch("supervisor.utils.dbus.DBus._init_proxy", new=mock_init_proxy), patch(
|
||||
"supervisor.dbus.manager.MessageBus.connect", return_value=dbus_bus
|
||||
):
|
||||
yield dbus_bus
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def dbus_is_connected():
|
||||
"""Mock DBusInterface.is_connected for tests."""
|
||||
|
@ -1,9 +1,12 @@
|
||||
"""Test dbus utility."""
|
||||
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
from dbus_fast.aio.message_bus import MessageBus
|
||||
import pytest
|
||||
|
||||
from supervisor.dbus.const import DBUS_OBJECT_BASE
|
||||
from supervisor.exceptions import DBusInterfaceError
|
||||
from supervisor.exceptions import DBusFatalError, DBusInterfaceError
|
||||
from supervisor.utils.dbus import DBus
|
||||
|
||||
|
||||
@ -14,3 +17,26 @@ async def test_missing_properties_interface(dbus_bus: MessageBus, dbus: list[str
|
||||
)
|
||||
with pytest.raises(DBusInterfaceError):
|
||||
await service.get_properties("test.no.properties.interface")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("err", [BrokenPipeError(), EOFError(), OSError()])
|
||||
async def test_internal_dbus_errors(
|
||||
dbus_minimal: MessageBus,
|
||||
capture_exception: Mock,
|
||||
err: Exception,
|
||||
):
|
||||
"""Test internal dbus library errors become dbus error."""
|
||||
rauc = await DBus.connect(dbus_minimal, "de.pengutronix.rauc", DBUS_OBJECT_BASE)
|
||||
setattr(
|
||||
# pylint: disable=protected-access
|
||||
rauc._proxies["de.pengutronix.rauc.Installer"],
|
||||
# pylint: enable=protected-access
|
||||
"call_mark",
|
||||
proxy_mock := AsyncMock().call_mark,
|
||||
)
|
||||
proxy_mock.side_effect = err
|
||||
|
||||
with pytest.raises(DBusFatalError):
|
||||
await rauc.Installer.call_mark("good", "booted")
|
||||
|
||||
capture_exception.assert_called_once_with(err)
|
||||
|
Loading…
x
Reference in New Issue
Block a user