chore: initial port
This commit is contained in:
0
tests/service/__init__.py
Normal file
0
tests/service/__init__.py
Normal file
151
tests/service/test_decorators.py
Normal file
151
tests/service/test_decorators.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from dbus_next import PropertyAccess
|
||||
from dbus_next import introspection as intr
|
||||
from dbus_next.service import ServiceInterface, dbus_property, method, signal
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self):
|
||||
super().__init__("test.interface")
|
||||
self._some_prop = 55
|
||||
self._another_prop = 101
|
||||
self._weird_prop = 500
|
||||
|
||||
@method()
|
||||
def some_method(self, one: "s", two: "s") -> "s":
|
||||
return "hello"
|
||||
|
||||
@method(name="renamed_method", disabled=True)
|
||||
def another_method(self, eight: "o", six: "t"):
|
||||
pass
|
||||
|
||||
@signal()
|
||||
def some_signal(self) -> "as":
|
||||
return ["result"]
|
||||
|
||||
@signal(name="renamed_signal", disabled=True)
|
||||
def another_signal(self) -> "(dodo)":
|
||||
return [1, "/", 1, "/"]
|
||||
|
||||
@dbus_property(
|
||||
name="renamed_readonly_property", access=PropertyAccess.READ, disabled=True
|
||||
)
|
||||
def another_prop(self) -> "t":
|
||||
return self._another_prop
|
||||
|
||||
@dbus_property()
|
||||
def some_prop(self) -> "u":
|
||||
return self._some_prop
|
||||
|
||||
@some_prop.setter
|
||||
def some_prop(self, val: "u"):
|
||||
self._some_prop = val + 1
|
||||
|
||||
# for this one, the setter has a different name than the getter which is a
|
||||
# special case in the code
|
||||
@dbus_property()
|
||||
def weird_prop(self) -> "t":
|
||||
return self._weird_prop
|
||||
|
||||
@weird_prop.setter
|
||||
def setter_for_weird_prop(self, val: "t"):
|
||||
self._weird_prop = val
|
||||
|
||||
|
||||
def test_method_decorator():
|
||||
interface = ExampleInterface()
|
||||
assert interface.name == "test.interface"
|
||||
|
||||
properties = ServiceInterface._get_properties(interface)
|
||||
methods = ServiceInterface._get_methods(interface)
|
||||
signals = ServiceInterface._get_signals(interface)
|
||||
|
||||
assert len(methods) == 2
|
||||
|
||||
method = methods[0]
|
||||
assert method.name == "renamed_method"
|
||||
assert method.in_signature == "ot"
|
||||
assert method.out_signature == ""
|
||||
assert method.disabled
|
||||
assert type(method.introspection) is intr.Method
|
||||
|
||||
method = methods[1]
|
||||
assert method.name == "some_method"
|
||||
assert method.in_signature == "ss"
|
||||
assert method.out_signature == "s"
|
||||
assert not method.disabled
|
||||
assert type(method.introspection) is intr.Method
|
||||
|
||||
assert len(signals) == 2
|
||||
|
||||
signal = signals[0]
|
||||
assert signal.name == "renamed_signal"
|
||||
assert signal.signature == "(dodo)"
|
||||
assert signal.disabled
|
||||
assert type(signal.introspection) is intr.Signal
|
||||
|
||||
signal = signals[1]
|
||||
assert signal.name == "some_signal"
|
||||
assert signal.signature == "as"
|
||||
assert not signal.disabled
|
||||
assert type(signal.introspection) is intr.Signal
|
||||
|
||||
assert len(properties) == 3
|
||||
|
||||
renamed_readonly_prop = properties[0]
|
||||
assert renamed_readonly_prop.name == "renamed_readonly_property"
|
||||
assert renamed_readonly_prop.signature == "t"
|
||||
assert renamed_readonly_prop.access == PropertyAccess.READ
|
||||
assert renamed_readonly_prop.disabled
|
||||
assert type(renamed_readonly_prop.introspection) is intr.Property
|
||||
|
||||
weird_prop = properties[1]
|
||||
assert weird_prop.name == "weird_prop"
|
||||
assert weird_prop.access == PropertyAccess.READWRITE
|
||||
assert weird_prop.signature == "t"
|
||||
assert not weird_prop.disabled
|
||||
assert weird_prop.prop_getter is not None
|
||||
assert weird_prop.prop_getter.__name__ == "weird_prop"
|
||||
assert weird_prop.prop_setter is not None
|
||||
assert weird_prop.prop_setter.__name__ == "setter_for_weird_prop"
|
||||
assert type(weird_prop.introspection) is intr.Property
|
||||
|
||||
prop = properties[2]
|
||||
assert prop.name == "some_prop"
|
||||
assert prop.access == PropertyAccess.READWRITE
|
||||
assert prop.signature == "u"
|
||||
assert not prop.disabled
|
||||
assert prop.prop_getter is not None
|
||||
assert prop.prop_setter is not None
|
||||
assert type(prop.introspection) is intr.Property
|
||||
|
||||
# make sure the getter and setter actually work
|
||||
assert interface._some_prop == 55
|
||||
interface._some_prop = 555
|
||||
assert interface.some_prop == 555
|
||||
|
||||
assert interface._weird_prop == 500
|
||||
assert weird_prop.prop_getter(interface) == 500
|
||||
interface._weird_prop = 1001
|
||||
assert interface._weird_prop == 1001
|
||||
weird_prop.prop_setter(interface, 600)
|
||||
assert interface._weird_prop == 600
|
||||
|
||||
|
||||
def test_interface_introspection():
|
||||
interface = ExampleInterface()
|
||||
intr_interface = interface.introspect()
|
||||
assert type(intr_interface) is intr.Interface
|
||||
|
||||
xml = intr_interface.to_xml()
|
||||
|
||||
assert xml.tag == "interface"
|
||||
assert xml.attrib.get("name", None) == "test.interface"
|
||||
|
||||
methods = xml.findall("method")
|
||||
signals = xml.findall("signal")
|
||||
properties = xml.findall("property")
|
||||
|
||||
assert len(xml) == 4
|
||||
assert len(methods) == 1
|
||||
assert len(signals) == 1
|
||||
assert len(properties) == 2
|
||||
118
tests/service/test_export.py
Normal file
118
tests/service/test_export.py
Normal file
@@ -0,0 +1,118 @@
|
||||
import pytest
|
||||
from dbus_next import Message, MessageType
|
||||
from dbus_next import introspection as intr
|
||||
from dbus_next.aio import MessageBus
|
||||
from dbus_next.service import ServiceInterface, method
|
||||
|
||||
standard_interfaces_count = len(intr.Node.default().interfaces)
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
self._method_called = False
|
||||
super().__init__(name)
|
||||
|
||||
@method()
|
||||
def some_method(self):
|
||||
self._method_called = True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_export_unexport():
|
||||
interface = ExampleInterface("test.interface")
|
||||
interface2 = ExampleInterface("test.interface2")
|
||||
|
||||
export_path = "/test/path"
|
||||
export_path2 = "/test/path/child"
|
||||
|
||||
bus = await MessageBus().connect()
|
||||
bus.export(export_path, interface)
|
||||
assert export_path in bus._path_exports
|
||||
assert len(bus._path_exports[export_path]) == 1
|
||||
assert bus._path_exports[export_path][0] is interface
|
||||
assert len(ServiceInterface._get_buses(interface)) == 1
|
||||
|
||||
bus.export(export_path2, interface2)
|
||||
|
||||
node = bus._introspect_export_path(export_path)
|
||||
assert len(node.interfaces) == standard_interfaces_count + 1
|
||||
assert len(node.nodes) == 1
|
||||
# relative path
|
||||
assert node.nodes[0].name == "child"
|
||||
|
||||
bus.unexport(export_path, interface)
|
||||
assert export_path not in bus._path_exports
|
||||
assert len(ServiceInterface._get_buses(interface)) == 0
|
||||
|
||||
bus.export(export_path2, interface)
|
||||
assert len(bus._path_exports[export_path2]) == 2
|
||||
|
||||
# test unexporting the whole path
|
||||
bus.unexport(export_path2)
|
||||
assert not bus._path_exports
|
||||
assert not ServiceInterface._get_buses(interface)
|
||||
assert not ServiceInterface._get_buses(interface2)
|
||||
|
||||
# test unexporting by name
|
||||
bus.export(export_path, interface)
|
||||
bus.unexport(export_path, interface.name)
|
||||
assert not bus._path_exports
|
||||
assert not ServiceInterface._get_buses(interface)
|
||||
|
||||
node = bus._introspect_export_path("/path/doesnt/exist")
|
||||
assert type(node) is intr.Node
|
||||
assert not node.interfaces
|
||||
assert not node.nodes
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_export_alias():
|
||||
bus = await MessageBus().connect()
|
||||
|
||||
interface = ExampleInterface("test.interface")
|
||||
|
||||
export_path = "/test/path"
|
||||
export_path2 = "/test/path/child"
|
||||
|
||||
bus.export(export_path, interface)
|
||||
bus.export(export_path2, interface)
|
||||
|
||||
result = await bus.call(
|
||||
Message(
|
||||
destination=bus.unique_name,
|
||||
path=export_path,
|
||||
interface="test.interface",
|
||||
member="some_method",
|
||||
)
|
||||
)
|
||||
assert result.message_type is MessageType.METHOD_RETURN, result.body[0]
|
||||
|
||||
assert interface._method_called
|
||||
interface._method_called = False
|
||||
|
||||
result = await bus.call(
|
||||
Message(
|
||||
destination=bus.unique_name,
|
||||
path=export_path2,
|
||||
interface="test.interface",
|
||||
member="some_method",
|
||||
)
|
||||
)
|
||||
assert result.message_type is MessageType.METHOD_RETURN, result.body[0]
|
||||
assert interface._method_called
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_export_introspection():
|
||||
interface = ExampleInterface("test.interface")
|
||||
interface2 = ExampleInterface("test.interface2")
|
||||
|
||||
export_path = "/test/path"
|
||||
export_path2 = "/test/path/child"
|
||||
|
||||
bus = await MessageBus().connect()
|
||||
bus.export(export_path, interface)
|
||||
bus.export(export_path2, interface2)
|
||||
|
||||
root = bus._introspect_export_path("/")
|
||||
assert len(root.nodes) == 1
|
||||
180
tests/service/test_methods.py
Normal file
180
tests/service/test_methods.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import pytest
|
||||
from dbus_next import (
|
||||
DBusError,
|
||||
ErrorType,
|
||||
Message,
|
||||
MessageFlag,
|
||||
MessageType,
|
||||
SignatureTree,
|
||||
Variant,
|
||||
)
|
||||
from dbus_next.aio import MessageBus
|
||||
from dbus_next.service import ServiceInterface, method
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
|
||||
@method()
|
||||
def echo(self, what: "s") -> "s":
|
||||
assert type(self) is ExampleInterface
|
||||
return what
|
||||
|
||||
@method()
|
||||
def echo_multiple(self, what1: "s", what2: "s") -> "ss":
|
||||
assert type(self) is ExampleInterface
|
||||
return [what1, what2]
|
||||
|
||||
@method()
|
||||
def echo_containers(
|
||||
self, array: "as", variant: "v", dict_entries: "a{sv}", struct: "(s(s(v)))"
|
||||
) -> "asva{sv}(s(s(v)))":
|
||||
assert type(self) is ExampleInterface
|
||||
return [array, variant, dict_entries, struct]
|
||||
|
||||
@method()
|
||||
def ping(self):
|
||||
assert type(self) is ExampleInterface
|
||||
pass
|
||||
|
||||
@method(name="renamed")
|
||||
def original_name(self):
|
||||
assert type(self) is ExampleInterface
|
||||
pass
|
||||
|
||||
@method(disabled=True)
|
||||
def not_here(self):
|
||||
assert type(self) is ExampleInterface
|
||||
pass
|
||||
|
||||
@method()
|
||||
def throws_unexpected_error(self):
|
||||
assert type(self) is ExampleInterface
|
||||
raise Exception("oops")
|
||||
|
||||
@method()
|
||||
def throws_dbus_error(self):
|
||||
assert type(self) is ExampleInterface
|
||||
raise DBusError("test.error", "an error ocurred")
|
||||
|
||||
|
||||
class AsyncInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
|
||||
@method()
|
||||
async def echo(self, what: "s") -> "s":
|
||||
assert type(self) is AsyncInterface
|
||||
return what
|
||||
|
||||
@method()
|
||||
async def echo_multiple(self, what1: "s", what2: "s") -> "ss":
|
||||
assert type(self) is AsyncInterface
|
||||
return [what1, what2]
|
||||
|
||||
@method()
|
||||
async def echo_containers(
|
||||
self, array: "as", variant: "v", dict_entries: "a{sv}", struct: "(s(s(v)))"
|
||||
) -> "asva{sv}(s(s(v)))":
|
||||
assert type(self) is AsyncInterface
|
||||
return [array, variant, dict_entries, struct]
|
||||
|
||||
@method()
|
||||
async def ping(self):
|
||||
assert type(self) is AsyncInterface
|
||||
pass
|
||||
|
||||
@method(name="renamed")
|
||||
async def original_name(self):
|
||||
assert type(self) is AsyncInterface
|
||||
pass
|
||||
|
||||
@method(disabled=True)
|
||||
async def not_here(self):
|
||||
assert type(self) is AsyncInterface
|
||||
pass
|
||||
|
||||
@method()
|
||||
async def throws_unexpected_error(self):
|
||||
assert type(self) is AsyncInterface
|
||||
raise Exception("oops")
|
||||
|
||||
@method()
|
||||
def throws_dbus_error(self):
|
||||
assert type(self) is AsyncInterface
|
||||
raise DBusError("test.error", "an error ocurred")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("interface_class", [ExampleInterface, AsyncInterface])
|
||||
@pytest.mark.asyncio
|
||||
async def test_methods(interface_class):
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
interface = interface_class("test.interface")
|
||||
export_path = "/test/path"
|
||||
|
||||
async def call(member, signature="", body=[], flags=MessageFlag.NONE):
|
||||
return await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path=export_path,
|
||||
interface=interface.name,
|
||||
member=member,
|
||||
signature=signature,
|
||||
body=body,
|
||||
flags=flags,
|
||||
)
|
||||
)
|
||||
|
||||
bus1.export(export_path, interface)
|
||||
|
||||
body = ["hello world"]
|
||||
reply = await call("echo", "s", body)
|
||||
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
|
||||
assert reply.signature == "s"
|
||||
assert reply.body == body
|
||||
|
||||
body = ["hello", "world"]
|
||||
reply = await call("echo_multiple", "ss", body)
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
|
||||
assert reply.signature == "ss"
|
||||
assert reply.body == body
|
||||
|
||||
body = [
|
||||
["hello", "world"],
|
||||
Variant("v", Variant("(ss)", ["hello", "world"])),
|
||||
{"foo": Variant("t", 100)},
|
||||
["one", ["two", [Variant("s", "three")]]],
|
||||
]
|
||||
signature = "asva{sv}(s(s(v)))"
|
||||
SignatureTree(signature).verify(body)
|
||||
reply = await call("echo_containers", signature, body)
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
|
||||
assert reply.signature == signature
|
||||
assert reply.body == body
|
||||
|
||||
reply = await call("ping")
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
|
||||
assert reply.signature == ""
|
||||
assert reply.body == []
|
||||
|
||||
reply = await call("throws_unexpected_error")
|
||||
assert reply.message_type == MessageType.ERROR, reply.body[0]
|
||||
assert reply.error_name == ErrorType.SERVICE_ERROR.value, reply.body[0]
|
||||
|
||||
reply = await call("throws_dbus_error")
|
||||
assert reply.message_type == MessageType.ERROR, reply.body[0]
|
||||
assert reply.error_name == "test.error", reply.body[0]
|
||||
assert reply.body == ["an error ocurred"]
|
||||
|
||||
reply = await call("ping", flags=MessageFlag.NO_REPLY_EXPECTED)
|
||||
assert reply is None
|
||||
|
||||
reply = await call("throws_unexpected_error", flags=MessageFlag.NO_REPLY_EXPECTED)
|
||||
assert reply is None
|
||||
|
||||
reply = await call("throws_dbus_error", flags=MessageFlag.NO_REPLY_EXPECTED)
|
||||
assert reply is None
|
||||
296
tests/service/test_properties.py
Normal file
296
tests/service/test_properties.py
Normal file
@@ -0,0 +1,296 @@
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
from dbus_next import (
|
||||
DBusError,
|
||||
ErrorType,
|
||||
Message,
|
||||
MessageType,
|
||||
PropertyAccess,
|
||||
Variant,
|
||||
)
|
||||
from dbus_next.aio import MessageBus
|
||||
from dbus_next.service import ServiceInterface, dbus_property, method
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
self._string_prop = "hi"
|
||||
self._readonly_prop = 100
|
||||
self._disabled_prop = "1234"
|
||||
self._container_prop = [["hello", "world"]]
|
||||
self._renamed_prop = "65"
|
||||
|
||||
@dbus_property()
|
||||
def string_prop(self) -> "s":
|
||||
return self._string_prop
|
||||
|
||||
@string_prop.setter
|
||||
def string_prop_setter(self, val: "s"):
|
||||
self._string_prop = val
|
||||
|
||||
@dbus_property(PropertyAccess.READ)
|
||||
def readonly_prop(self) -> "t":
|
||||
return self._readonly_prop
|
||||
|
||||
@dbus_property()
|
||||
def container_prop(self) -> "a(ss)":
|
||||
return self._container_prop
|
||||
|
||||
@container_prop.setter
|
||||
def container_prop(self, val: "a(ss)"):
|
||||
self._container_prop = val
|
||||
|
||||
@dbus_property(name="renamed_prop")
|
||||
def original_name(self) -> "s":
|
||||
return self._renamed_prop
|
||||
|
||||
@original_name.setter
|
||||
def original_name_setter(self, val: "s"):
|
||||
self._renamed_prop = val
|
||||
|
||||
@dbus_property(disabled=True)
|
||||
def disabled_prop(self) -> "s":
|
||||
return self._disabled_prop
|
||||
|
||||
@disabled_prop.setter
|
||||
def disabled_prop(self, val: "s"):
|
||||
self._disabled_prop = val
|
||||
|
||||
@dbus_property(disabled=True)
|
||||
def throws_error(self) -> "s":
|
||||
raise DBusError("test.error", "told you so")
|
||||
|
||||
@throws_error.setter
|
||||
def throws_error(self, val: "s"):
|
||||
raise DBusError("test.error", "told you so")
|
||||
|
||||
@dbus_property(PropertyAccess.READ, disabled=True)
|
||||
def returns_wrong_type(self) -> "s":
|
||||
return 5
|
||||
|
||||
@method()
|
||||
def do_emit_properties_changed(self):
|
||||
changed = {"string_prop": "asdf"}
|
||||
invalidated = ["container_prop"]
|
||||
self.emit_properties_changed(changed, invalidated)
|
||||
|
||||
|
||||
class AsyncInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
self._string_prop = "hi"
|
||||
self._readonly_prop = 100
|
||||
self._disabled_prop = "1234"
|
||||
self._container_prop = [["hello", "world"]]
|
||||
self._renamed_prop = "65"
|
||||
|
||||
@dbus_property()
|
||||
async def string_prop(self) -> "s":
|
||||
return self._string_prop
|
||||
|
||||
@string_prop.setter
|
||||
async def string_prop_setter(self, val: "s"):
|
||||
self._string_prop = val
|
||||
|
||||
@dbus_property(PropertyAccess.READ)
|
||||
async def readonly_prop(self) -> "t":
|
||||
return self._readonly_prop
|
||||
|
||||
@dbus_property()
|
||||
async def container_prop(self) -> "a(ss)":
|
||||
return self._container_prop
|
||||
|
||||
@container_prop.setter
|
||||
async def container_prop(self, val: "a(ss)"):
|
||||
self._container_prop = val
|
||||
|
||||
@dbus_property(name="renamed_prop")
|
||||
async def original_name(self) -> "s":
|
||||
return self._renamed_prop
|
||||
|
||||
@original_name.setter
|
||||
async def original_name_setter(self, val: "s"):
|
||||
self._renamed_prop = val
|
||||
|
||||
@dbus_property(disabled=True)
|
||||
async def disabled_prop(self) -> "s":
|
||||
return self._disabled_prop
|
||||
|
||||
@disabled_prop.setter
|
||||
async def disabled_prop(self, val: "s"):
|
||||
self._disabled_prop = val
|
||||
|
||||
@dbus_property(disabled=True)
|
||||
async def throws_error(self) -> "s":
|
||||
raise DBusError("test.error", "told you so")
|
||||
|
||||
@throws_error.setter
|
||||
async def throws_error(self, val: "s"):
|
||||
raise DBusError("test.error", "told you so")
|
||||
|
||||
@dbus_property(PropertyAccess.READ, disabled=True)
|
||||
async def returns_wrong_type(self) -> "s":
|
||||
return 5
|
||||
|
||||
@method()
|
||||
def do_emit_properties_changed(self):
|
||||
changed = {"string_prop": "asdf"}
|
||||
invalidated = ["container_prop"]
|
||||
self.emit_properties_changed(changed, invalidated)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("interface_class", [ExampleInterface, AsyncInterface])
|
||||
@pytest.mark.asyncio
|
||||
async def test_property_methods(interface_class):
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
interface = interface_class("test.interface")
|
||||
export_path = "/test/path"
|
||||
bus1.export(export_path, interface)
|
||||
|
||||
async def call_properties(member, signature, body):
|
||||
return await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path=export_path,
|
||||
interface="org.freedesktop.DBus.Properties",
|
||||
member=member,
|
||||
signature=signature,
|
||||
body=body,
|
||||
)
|
||||
)
|
||||
|
||||
result = await call_properties("GetAll", "s", [interface.name])
|
||||
|
||||
assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
|
||||
assert result.signature == "a{sv}"
|
||||
assert result.body == [
|
||||
{
|
||||
"string_prop": Variant("s", interface._string_prop),
|
||||
"readonly_prop": Variant("t", interface._readonly_prop),
|
||||
"container_prop": Variant("a(ss)", interface._container_prop),
|
||||
"renamed_prop": Variant("s", interface._renamed_prop),
|
||||
}
|
||||
]
|
||||
|
||||
result = await call_properties("Get", "ss", [interface.name, "string_prop"])
|
||||
assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
|
||||
assert result.signature == "v"
|
||||
assert result.body == [Variant("s", "hi")]
|
||||
|
||||
result = await call_properties(
|
||||
"Set", "ssv", [interface.name, "string_prop", Variant("s", "ho")]
|
||||
)
|
||||
assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
|
||||
assert interface._string_prop == "ho"
|
||||
if interface_class is AsyncInterface:
|
||||
assert "ho", await interface.string_prop()
|
||||
else:
|
||||
assert "ho", interface.string_prop
|
||||
|
||||
result = await call_properties(
|
||||
"Set", "ssv", [interface.name, "readonly_prop", Variant("t", 100)]
|
||||
)
|
||||
assert result.message_type == MessageType.ERROR, result.body[0]
|
||||
assert result.error_name == ErrorType.PROPERTY_READ_ONLY.value, result.body[0]
|
||||
|
||||
result = await call_properties(
|
||||
"Set", "ssv", [interface.name, "disabled_prop", Variant("s", "asdf")]
|
||||
)
|
||||
assert result.message_type == MessageType.ERROR, result.body[0]
|
||||
assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value
|
||||
|
||||
result = await call_properties(
|
||||
"Set", "ssv", [interface.name, "not_a_prop", Variant("s", "asdf")]
|
||||
)
|
||||
assert result.message_type == MessageType.ERROR, result.body[0]
|
||||
assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value
|
||||
|
||||
# wrong type
|
||||
result = await call_properties(
|
||||
"Set", "ssv", [interface.name, "string_prop", Variant("t", 100)]
|
||||
)
|
||||
assert result.message_type == MessageType.ERROR
|
||||
assert result.error_name == ErrorType.INVALID_SIGNATURE.value
|
||||
|
||||
# enable the erroring properties so we can test them
|
||||
for prop in ServiceInterface._get_properties(interface):
|
||||
if prop.name in ["throws_error", "returns_wrong_type"]:
|
||||
prop.disabled = False
|
||||
|
||||
result = await call_properties("Get", "ss", [interface.name, "returns_wrong_type"])
|
||||
assert result.message_type == MessageType.ERROR, result.body[0]
|
||||
assert result.error_name == ErrorType.SERVICE_ERROR.value
|
||||
|
||||
result = await call_properties(
|
||||
"Set", "ssv", [interface.name, "throws_error", Variant("s", "ho")]
|
||||
)
|
||||
assert result.message_type == MessageType.ERROR, result.body[0]
|
||||
assert result.error_name == "test.error"
|
||||
assert result.body == ["told you so"]
|
||||
|
||||
result = await call_properties("Get", "ss", [interface.name, "throws_error"])
|
||||
assert result.message_type == MessageType.ERROR, result.body[0]
|
||||
assert result.error_name == "test.error"
|
||||
assert result.body == ["told you so"]
|
||||
|
||||
result = await call_properties("GetAll", "s", [interface.name])
|
||||
assert result.message_type == MessageType.ERROR, result.body[0]
|
||||
assert result.error_name == "test.error"
|
||||
assert result.body == ["told you so"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("interface_class", [ExampleInterface, AsyncInterface])
|
||||
@pytest.mark.asyncio
|
||||
async def test_property_changed_signal(interface_class):
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
await bus2.call(
|
||||
Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="AddMatch",
|
||||
signature="s",
|
||||
body=[f"sender={bus1.unique_name}"],
|
||||
)
|
||||
)
|
||||
|
||||
interface = interface_class("test.interface")
|
||||
export_path = "/test/path"
|
||||
bus1.export(export_path, interface)
|
||||
|
||||
async def wait_for_message():
|
||||
# TODO timeout
|
||||
future = asyncio.get_event_loop().create_future()
|
||||
|
||||
def message_handler(signal):
|
||||
if signal.interface == "org.freedesktop.DBus.Properties":
|
||||
bus2.remove_message_handler(message_handler)
|
||||
future.set_result(signal)
|
||||
|
||||
bus2.add_message_handler(message_handler)
|
||||
return await future
|
||||
|
||||
bus2.send(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
interface=interface.name,
|
||||
path=export_path,
|
||||
member="do_emit_properties_changed",
|
||||
)
|
||||
)
|
||||
|
||||
signal = await wait_for_message()
|
||||
assert signal.interface == "org.freedesktop.DBus.Properties"
|
||||
assert signal.member == "PropertiesChanged"
|
||||
assert signal.signature == "sa{sv}as"
|
||||
assert signal.body == [
|
||||
interface.name,
|
||||
{"string_prop": Variant("s", "asdf")},
|
||||
["container_prop"],
|
||||
]
|
||||
250
tests/service/test_signals.py
Normal file
250
tests/service/test_signals.py
Normal file
@@ -0,0 +1,250 @@
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
from dbus_next import Message, MessageType
|
||||
from dbus_next.aio import MessageBus
|
||||
from dbus_next.constants import PropertyAccess
|
||||
from dbus_next.service import (
|
||||
ServiceInterface,
|
||||
SignalDisabledError,
|
||||
dbus_property,
|
||||
signal,
|
||||
)
|
||||
from dbus_next.signature import Variant
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
|
||||
@signal()
|
||||
def signal_empty(self):
|
||||
assert type(self) is ExampleInterface
|
||||
|
||||
@signal()
|
||||
def signal_simple(self) -> "s":
|
||||
assert type(self) is ExampleInterface
|
||||
return "hello"
|
||||
|
||||
@signal()
|
||||
def signal_multiple(self) -> "ss":
|
||||
assert type(self) is ExampleInterface
|
||||
return ["hello", "world"]
|
||||
|
||||
@signal(name="renamed")
|
||||
def original_name(self):
|
||||
assert type(self) is ExampleInterface
|
||||
|
||||
@signal(disabled=True)
|
||||
def signal_disabled(self):
|
||||
assert type(self) is ExampleInterface
|
||||
|
||||
@dbus_property(access=PropertyAccess.READ)
|
||||
def test_prop(self) -> "i":
|
||||
return 42
|
||||
|
||||
|
||||
class SecondExampleInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
|
||||
@dbus_property(access=PropertyAccess.READ)
|
||||
def str_prop(self) -> "s":
|
||||
return "abc"
|
||||
|
||||
@dbus_property(access=PropertyAccess.READ)
|
||||
def list_prop(self) -> "ai":
|
||||
return [1, 2, 3]
|
||||
|
||||
|
||||
class ExpectMessage:
|
||||
def __init__(self, bus1, bus2, interface_name, timeout=1):
|
||||
self.future = asyncio.get_event_loop().create_future()
|
||||
self.bus1 = bus1
|
||||
self.bus2 = bus2
|
||||
self.interface_name = interface_name
|
||||
self.timeout = timeout
|
||||
self.timeout_task = None
|
||||
|
||||
def message_handler(self, msg):
|
||||
if msg.sender == self.bus1.unique_name and msg.interface == self.interface_name:
|
||||
self.timeout_task.cancel()
|
||||
self.future.set_result(msg)
|
||||
return True
|
||||
|
||||
def timeout_cb(self):
|
||||
self.future.set_exception(TimeoutError)
|
||||
|
||||
async def __aenter__(self):
|
||||
self.bus2.add_message_handler(self.message_handler)
|
||||
self.timeout_task = asyncio.get_event_loop().call_later(
|
||||
self.timeout, self.timeout_cb
|
||||
)
|
||||
|
||||
return self.future
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
self.bus2.remove_message_handler(self.message_handler)
|
||||
|
||||
|
||||
def assert_signal_ok(signal, export_path, member, signature, body):
|
||||
assert signal.message_type == MessageType.SIGNAL
|
||||
assert signal.path == export_path
|
||||
assert signal.member == member
|
||||
assert signal.signature == signature
|
||||
assert signal.body == body
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_signals():
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
interface = ExampleInterface("test.interface")
|
||||
export_path = "/test/path"
|
||||
bus1.export(export_path, interface)
|
||||
|
||||
await bus2.call(
|
||||
Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="AddMatch",
|
||||
signature="s",
|
||||
body=[f"sender={bus1.unique_name}"],
|
||||
)
|
||||
)
|
||||
|
||||
async with ExpectMessage(bus1, bus2, interface.name) as expected_signal:
|
||||
interface.signal_empty()
|
||||
assert_signal_ok(
|
||||
signal=await expected_signal,
|
||||
export_path=export_path,
|
||||
member="signal_empty",
|
||||
signature="",
|
||||
body=[],
|
||||
)
|
||||
|
||||
async with ExpectMessage(bus1, bus2, interface.name) as expected_signal:
|
||||
interface.original_name()
|
||||
assert_signal_ok(
|
||||
signal=await expected_signal,
|
||||
export_path=export_path,
|
||||
member="renamed",
|
||||
signature="",
|
||||
body=[],
|
||||
)
|
||||
|
||||
async with ExpectMessage(bus1, bus2, interface.name) as expected_signal:
|
||||
interface.signal_simple()
|
||||
assert_signal_ok(
|
||||
signal=await expected_signal,
|
||||
export_path=export_path,
|
||||
member="signal_simple",
|
||||
signature="s",
|
||||
body=["hello"],
|
||||
)
|
||||
|
||||
async with ExpectMessage(bus1, bus2, interface.name) as expected_signal:
|
||||
interface.signal_multiple()
|
||||
assert_signal_ok(
|
||||
signal=await expected_signal,
|
||||
export_path=export_path,
|
||||
member="signal_multiple",
|
||||
signature="ss",
|
||||
body=["hello", "world"],
|
||||
)
|
||||
|
||||
with pytest.raises(SignalDisabledError):
|
||||
interface.signal_disabled()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_interface_add_remove_signal():
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
await bus2.call(
|
||||
Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="AddMatch",
|
||||
signature="s",
|
||||
body=[f"sender={bus1.unique_name}"],
|
||||
)
|
||||
)
|
||||
|
||||
first_interface = ExampleInterface("test.interface.first")
|
||||
second_interface = SecondExampleInterface("test.interface.second")
|
||||
export_path = "/test/path"
|
||||
|
||||
# add first interface
|
||||
async with ExpectMessage(
|
||||
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
|
||||
) as expected_signal:
|
||||
bus1.export(export_path, first_interface)
|
||||
assert_signal_ok(
|
||||
signal=await expected_signal,
|
||||
export_path=export_path,
|
||||
member="InterfacesAdded",
|
||||
signature="oa{sa{sv}}",
|
||||
body=[
|
||||
export_path,
|
||||
{"test.interface.first": {"test_prop": Variant("i", 42)}},
|
||||
],
|
||||
)
|
||||
|
||||
# add second interface
|
||||
async with ExpectMessage(
|
||||
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
|
||||
) as expected_signal:
|
||||
bus1.export(export_path, second_interface)
|
||||
assert_signal_ok(
|
||||
signal=await expected_signal,
|
||||
export_path=export_path,
|
||||
member="InterfacesAdded",
|
||||
signature="oa{sa{sv}}",
|
||||
body=[
|
||||
export_path,
|
||||
{
|
||||
"test.interface.second": {
|
||||
"str_prop": Variant("s", "abc"),
|
||||
"list_prop": Variant("ai", [1, 2, 3]),
|
||||
}
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
# remove single interface
|
||||
async with ExpectMessage(
|
||||
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
|
||||
) as expected_signal:
|
||||
bus1.unexport(export_path, second_interface)
|
||||
assert_signal_ok(
|
||||
signal=await expected_signal,
|
||||
export_path=export_path,
|
||||
member="InterfacesRemoved",
|
||||
signature="oas",
|
||||
body=[export_path, ["test.interface.second"]],
|
||||
)
|
||||
|
||||
# add second interface again
|
||||
async with ExpectMessage(
|
||||
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
|
||||
) as expected_signal:
|
||||
bus1.export(export_path, second_interface)
|
||||
await expected_signal
|
||||
|
||||
# remove multiple interfaces
|
||||
async with ExpectMessage(
|
||||
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
|
||||
) as expected_signal:
|
||||
bus1.unexport(export_path)
|
||||
assert_signal_ok(
|
||||
signal=await expected_signal,
|
||||
export_path=export_path,
|
||||
member="InterfacesRemoved",
|
||||
signature="oas",
|
||||
body=[export_path, ["test.interface.first", "test.interface.second"]],
|
||||
)
|
||||
237
tests/service/test_standard_interfaces.py
Normal file
237
tests/service/test_standard_interfaces.py
Normal file
@@ -0,0 +1,237 @@
|
||||
import pytest
|
||||
from dbus_next import Message, MessageType
|
||||
from dbus_next import introspection as intr
|
||||
from dbus_next.aio import MessageBus
|
||||
from dbus_next.constants import ErrorType
|
||||
from dbus_next.service import PropertyAccess, ServiceInterface, dbus_property
|
||||
from dbus_next.signature import Variant
|
||||
|
||||
standard_interfaces_count = len(intr.Node.default().interfaces)
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
|
||||
|
||||
class ExampleComplexInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
self._foo = 42
|
||||
self._bar = "str"
|
||||
self._async_prop = "async"
|
||||
super().__init__(name)
|
||||
|
||||
@dbus_property(access=PropertyAccess.READ)
|
||||
def Foo(self) -> "y":
|
||||
return self._foo
|
||||
|
||||
@dbus_property(access=PropertyAccess.READ)
|
||||
def Bar(self) -> "s":
|
||||
return self._bar
|
||||
|
||||
@dbus_property(access=PropertyAccess.READ)
|
||||
async def AsyncProp(self) -> "s":
|
||||
return self._async_prop
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_introspectable_interface():
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
interface = ExampleInterface("test.interface")
|
||||
interface2 = ExampleInterface("test.interface2")
|
||||
|
||||
export_path = "/test/path"
|
||||
bus1.export(export_path, interface)
|
||||
bus1.export(export_path, interface2)
|
||||
|
||||
reply = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path=export_path,
|
||||
interface="org.freedesktop.DBus.Introspectable",
|
||||
member="Introspect",
|
||||
)
|
||||
)
|
||||
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
|
||||
assert reply.signature == "s"
|
||||
node = intr.Node.parse(reply.body[0])
|
||||
assert len(node.interfaces) == standard_interfaces_count + 2
|
||||
assert node.interfaces[-1].name == "test.interface2"
|
||||
assert node.interfaces[-2].name == "test.interface"
|
||||
assert not node.nodes
|
||||
|
||||
# introspect works on every path
|
||||
reply = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path="/path/doesnt/exist",
|
||||
interface="org.freedesktop.DBus.Introspectable",
|
||||
member="Introspect",
|
||||
)
|
||||
)
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
|
||||
assert reply.signature == "s"
|
||||
node = intr.Node.parse(reply.body[0])
|
||||
assert not node.interfaces
|
||||
assert not node.nodes
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_peer_interface():
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
reply = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path="/path/doesnt/exist",
|
||||
interface="org.freedesktop.DBus.Peer",
|
||||
member="Ping",
|
||||
)
|
||||
)
|
||||
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
|
||||
assert reply.signature == ""
|
||||
|
||||
reply = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path="/path/doesnt/exist",
|
||||
interface="org.freedesktop.DBus.Peer",
|
||||
member="GetMachineId",
|
||||
signature="",
|
||||
)
|
||||
)
|
||||
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
|
||||
assert reply.signature == "s"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_object_manager():
|
||||
expected_reply = {
|
||||
"/test/path/deeper": {
|
||||
"test.interface2": {
|
||||
"Bar": Variant("s", "str"),
|
||||
"Foo": Variant("y", 42),
|
||||
"AsyncProp": Variant("s", "async"),
|
||||
}
|
||||
}
|
||||
}
|
||||
reply_ext = {
|
||||
"/test/path": {
|
||||
"test.interface1": {},
|
||||
"test.interface2": {
|
||||
"Bar": Variant("s", "str"),
|
||||
"Foo": Variant("y", 42),
|
||||
"AsyncProp": Variant("s", "async"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
interface = ExampleInterface("test.interface1")
|
||||
interface2 = ExampleComplexInterface("test.interface2")
|
||||
|
||||
export_path = "/test/path"
|
||||
bus1.export(export_path, interface)
|
||||
bus1.export(export_path, interface2)
|
||||
bus1.export(export_path + "/deeper", interface2)
|
||||
|
||||
reply_root = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path="/",
|
||||
interface="org.freedesktop.DBus.ObjectManager",
|
||||
member="GetManagedObjects",
|
||||
)
|
||||
)
|
||||
|
||||
reply_level1 = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path=export_path,
|
||||
interface="org.freedesktop.DBus.ObjectManager",
|
||||
member="GetManagedObjects",
|
||||
)
|
||||
)
|
||||
|
||||
reply_level2 = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path=export_path + "/deeper",
|
||||
interface="org.freedesktop.DBus.ObjectManager",
|
||||
member="GetManagedObjects",
|
||||
)
|
||||
)
|
||||
|
||||
assert reply_root.signature == "a{oa{sa{sv}}}"
|
||||
assert reply_level1.signature == "a{oa{sa{sv}}}"
|
||||
assert reply_level2.signature == "a{oa{sa{sv}}}"
|
||||
|
||||
assert reply_level2.body == [{}]
|
||||
assert reply_level1.body == [expected_reply]
|
||||
expected_reply.update(reply_ext)
|
||||
assert reply_root.body == [expected_reply]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_standard_interface_properties():
|
||||
# standard interfaces have no properties, but should still behave correctly
|
||||
# when you try to call the methods anyway (#49)
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
interface = ExampleInterface("test.interface1")
|
||||
export_path = "/test/path"
|
||||
bus1.export(export_path, interface)
|
||||
|
||||
for iface in [
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"org.freedesktop.DBus.Introspectable",
|
||||
"org.freedesktop.DBus.Peer",
|
||||
"org.freedesktop.DBus.ObjectManager",
|
||||
]:
|
||||
|
||||
result = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path=export_path,
|
||||
interface="org.freedesktop.DBus.Properties",
|
||||
member="Get",
|
||||
signature="ss",
|
||||
body=[iface, "anything"],
|
||||
)
|
||||
)
|
||||
assert result.message_type is MessageType.ERROR
|
||||
assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value
|
||||
|
||||
result = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path=export_path,
|
||||
interface="org.freedesktop.DBus.Properties",
|
||||
member="Set",
|
||||
signature="ssv",
|
||||
body=[iface, "anything", Variant("s", "new thing")],
|
||||
)
|
||||
)
|
||||
assert result.message_type is MessageType.ERROR
|
||||
assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value
|
||||
|
||||
result = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path=export_path,
|
||||
interface="org.freedesktop.DBus.Properties",
|
||||
member="GetAll",
|
||||
signature="s",
|
||||
body=[iface],
|
||||
)
|
||||
)
|
||||
assert result.message_type is MessageType.METHOD_RETURN
|
||||
assert result.body == [{}]
|
||||
Reference in New Issue
Block a user