mirror of
https://github.com/home-assistant/supervisor.git
synced 2026-04-02 08:12:47 +01:00
* Use Python 3.14(.3) in CI and base image Update base image to the latest tag using Python 3.14.3 and update Python version in CI workflows to 3.14. With Python 3.14, backports.zstd is no longer necessary as it's now available in the standard library. * Update wheels ABI in the wheels builder to cp314 * Use explicit Python fix version in GH actions Specify explicitly Python 3.14.3, as the setup-python action otherwise default to 3.14.2 when 3.14.3, leading to different version in CI and in production. * Update Python version references in pyproject.toml * Fix all ruff quoted-annotation (UP037) errors * Revert unquoting of DBus types in tests and ignore UP037 where needed
244 lines
7.3 KiB
Python
244 lines
7.3 KiB
Python
"""Test dbus interface."""
|
|
|
|
import asyncio
|
|
from unittest.mock import patch
|
|
|
|
from dbus_fast.aio.message_bus import MessageBus
|
|
from dbus_fast.service import PropertyAccess, dbus_property, signal
|
|
import pytest
|
|
|
|
from supervisor.dbus.const import DBUS_OBJECT_BASE
|
|
from supervisor.dbus.interface import DBusInterface, DBusInterfaceProxy
|
|
from supervisor.exceptions import DBusInterfaceError, DBusNotConnectedError
|
|
from supervisor.utils.dbus import DBus
|
|
|
|
from tests.common import load_fixture
|
|
from tests.dbus_service_mocks.base import DBusServiceMock
|
|
|
|
|
|
class TestInterface(DBusServiceMock):
|
|
"""Test interface."""
|
|
|
|
__test__ = False
|
|
interface = "service.test.TestInterface"
|
|
|
|
def __init__(self, object_path: str = "/service/test/TestInterface"):
|
|
"""Initialize object."""
|
|
super().__init__()
|
|
self.object_path = object_path
|
|
|
|
@signal(name="TestSignal")
|
|
def test_signal(self, value: str) -> "s": # noqa: F821, UP037
|
|
"""Send test signal."""
|
|
return value
|
|
|
|
@dbus_property(access=PropertyAccess.READ, name="TestProp")
|
|
def test_prop(self) -> "u": # noqa: F821, UP037
|
|
"""Get test property."""
|
|
return 4
|
|
|
|
|
|
class ServiceTest(DBusInterfaceProxy):
|
|
"""DBus test class."""
|
|
|
|
bus_name = "service.test.TestInterface"
|
|
object_path = "/service/test/TestInterface"
|
|
properties_interface = "service.test.TestInterface"
|
|
|
|
|
|
@pytest.fixture(name="test_service")
|
|
async def fixture_test_service(dbus_session_bus: MessageBus) -> TestInterface:
|
|
"""Export test interface on dbus."""
|
|
await dbus_session_bus.request_name("service.test.TestInterface")
|
|
service = TestInterface()
|
|
service.export(dbus_session_bus)
|
|
yield service
|
|
|
|
|
|
@pytest.fixture(name="proxy")
|
|
async def fixture_proxy(
|
|
request: pytest.FixtureRequest,
|
|
test_service: TestInterface,
|
|
dbus_session_bus: MessageBus,
|
|
) -> DBusInterfaceProxy:
|
|
"""Get a proxy."""
|
|
proxy = ServiceTest()
|
|
proxy.sync_properties = getattr(request, "param", True)
|
|
await proxy.connect(dbus_session_bus)
|
|
yield proxy
|
|
|
|
|
|
async def test_dbus_proxy_connect(
|
|
proxy: DBusInterfaceProxy, test_service: TestInterface
|
|
):
|
|
"""Test dbus proxy connect."""
|
|
assert proxy.is_connected
|
|
assert proxy.properties["TestProp"] == 4
|
|
|
|
test_service.emit_properties_changed({"TestProp": 1})
|
|
await test_service.ping()
|
|
assert proxy.properties["TestProp"] == 1
|
|
|
|
test_service.emit_properties_changed({}, ["TestProp"])
|
|
await test_service.ping()
|
|
await test_service.ping()
|
|
assert proxy.properties["TestProp"] == 4
|
|
|
|
|
|
@pytest.mark.parametrize("proxy", [False], indirect=True)
|
|
async def test_dbus_proxy_connect_no_sync(
|
|
proxy: DBusInterfaceProxy, test_service: TestInterface
|
|
):
|
|
"""Test dbus proxy connect with no properties sync."""
|
|
assert proxy.is_connected
|
|
assert proxy.properties["TestProp"] == 4
|
|
|
|
test_service.emit_properties_changed({"TestProp": 1})
|
|
await test_service.ping()
|
|
assert proxy.properties["TestProp"] == 4
|
|
|
|
|
|
@pytest.mark.parametrize("proxy", [False], indirect=True)
|
|
async def test_signal_listener_disconnect(
|
|
proxy: DBusInterfaceProxy, test_service: TestInterface
|
|
):
|
|
"""Test disconnect/delete unattaches signal listeners."""
|
|
value = None
|
|
|
|
async def callback(val: str):
|
|
nonlocal value
|
|
value = val
|
|
|
|
assert proxy.is_connected
|
|
proxy.dbus.on_test_signal(callback)
|
|
|
|
test_service.test_signal("hello")
|
|
await test_service.ping()
|
|
assert value == "hello"
|
|
|
|
proxy.disconnect()
|
|
test_service.test_signal("goodbye")
|
|
await test_service.ping()
|
|
assert value == "hello"
|
|
|
|
|
|
async def test_dbus_connected_no_raise_after_shutdown(
|
|
test_service: TestInterface, dbus_session_bus: MessageBus
|
|
):
|
|
"""Test dbus connected methods do not raise DBusNotConnectedError after shutdown."""
|
|
proxy = ServiceTest()
|
|
proxy.sync_properties = False
|
|
|
|
with pytest.raises(DBusNotConnectedError):
|
|
await proxy.update()
|
|
|
|
await proxy.connect(dbus_session_bus)
|
|
assert proxy.is_connected
|
|
|
|
proxy.shutdown()
|
|
assert proxy.is_shutdown
|
|
assert await proxy.update() is None
|
|
|
|
|
|
async def test_proxy_missing_properties_interface(dbus_session_bus: MessageBus):
|
|
"""Test proxy instance disconnects and errors when missing properties interface."""
|
|
|
|
class NoPropertiesService(DBusInterfaceProxy):
|
|
bus_name = "test.no.properties.interface"
|
|
object_path = DBUS_OBJECT_BASE
|
|
properties_interface = "test.no.properties.interface"
|
|
|
|
proxy = NoPropertiesService()
|
|
|
|
def mock_introspect(*args, **kwargs):
|
|
"""Return introspection without properties."""
|
|
return asyncio.get_running_loop().run_in_executor(
|
|
None, load_fixture, "test_no_properties_interface.xml"
|
|
)
|
|
|
|
with (
|
|
patch.object(MessageBus, "introspect", new=mock_introspect),
|
|
pytest.raises(DBusInterfaceError),
|
|
):
|
|
await proxy.connect(dbus_session_bus)
|
|
|
|
assert proxy.is_connected is False
|
|
|
|
|
|
async def test_initialize(test_service: TestInterface, dbus_session_bus: MessageBus):
|
|
"""Test initialize for reusing connected dbus object."""
|
|
|
|
class ServiceTestInterfaceOnly(DBusInterface):
|
|
bus_name = "service.test.TestInterface"
|
|
object_path = "/service/test/TestInterface"
|
|
|
|
proxy = ServiceTestInterfaceOnly()
|
|
assert proxy.is_connected is False
|
|
|
|
# Not connected
|
|
with pytest.raises(ValueError, match="must be a connected DBus object"):
|
|
await proxy.initialize(
|
|
DBus(
|
|
dbus_session_bus,
|
|
"service.test.TestInterface",
|
|
"/service/test/TestInterface",
|
|
)
|
|
)
|
|
|
|
# Connected to wrong bus
|
|
await dbus_session_bus.request_name("service.test.TestInterface2")
|
|
with pytest.raises(
|
|
ValueError,
|
|
match="must be a DBus object connected to bus service.test.TestInterface and object /service/test/TestInterface",
|
|
):
|
|
await proxy.initialize(
|
|
await DBus.connect(
|
|
dbus_session_bus,
|
|
"service.test.TestInterface2",
|
|
"/service/test/TestInterface",
|
|
)
|
|
)
|
|
|
|
# Connected to wrong object
|
|
test_service_2 = TestInterface("/service/test/TestInterface/2")
|
|
test_service_2.export(dbus_session_bus)
|
|
with pytest.raises(
|
|
ValueError,
|
|
match="must be a DBus object connected to bus service.test.TestInterface and object /service/test/TestInterface",
|
|
):
|
|
await proxy.initialize(
|
|
await DBus.connect(
|
|
dbus_session_bus,
|
|
"service.test.TestInterface",
|
|
"/service/test/TestInterface/2",
|
|
)
|
|
)
|
|
|
|
# Connected to correct object on the correct bus
|
|
await proxy.initialize(
|
|
await DBus.connect(
|
|
dbus_session_bus,
|
|
"service.test.TestInterface",
|
|
"/service/test/TestInterface",
|
|
)
|
|
)
|
|
assert proxy.is_connected is True
|
|
|
|
|
|
async def test_stop_sync_property_changes(
|
|
proxy: DBusInterfaceProxy, test_service: TestInterface
|
|
):
|
|
"""Test stop sync property changes disables the sync via signal."""
|
|
assert proxy.is_connected
|
|
assert proxy.properties["TestProp"] == 4
|
|
|
|
test_service.emit_properties_changed({"TestProp": 1})
|
|
await test_service.ping()
|
|
assert proxy.properties["TestProp"] == 1
|
|
|
|
proxy.stop_sync_property_changes()
|
|
|
|
test_service.emit_properties_changed({"TestProp": 4})
|
|
await test_service.ping()
|
|
assert proxy.properties["TestProp"] == 1
|