chore: initial port

This commit is contained in:
J. Nick Koston
2022-09-09 08:43:26 -05:00
parent 169581f691
commit 495bfac17f
84 changed files with 10876 additions and 17 deletions

View File

View 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

View 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

View 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

View 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"],
]

View 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"]],
)

View 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 == [{}]