chore: initial port
This commit is contained in:
134
tests/client/test_methods.py
Normal file
134
tests/client/test_methods.py
Normal file
@@ -0,0 +1,134 @@
|
||||
from test.util import check_gi_repository, skip_reason_no_gi
|
||||
|
||||
import dbus_next.introspection as intr
|
||||
import pytest
|
||||
from dbus_next import DBusError, aio, glib
|
||||
from dbus_next.message import MessageFlag
|
||||
from dbus_next.service import ServiceInterface, method
|
||||
|
||||
has_gi = check_gi_repository()
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self):
|
||||
super().__init__("test.interface")
|
||||
|
||||
@method()
|
||||
def Ping(self):
|
||||
pass
|
||||
|
||||
@method()
|
||||
def EchoInt64(self, what: "x") -> "x":
|
||||
return what
|
||||
|
||||
@method()
|
||||
def EchoString(self, what: "s") -> "s":
|
||||
return what
|
||||
|
||||
@method()
|
||||
def ConcatStrings(self, what1: "s", what2: "s") -> "s":
|
||||
return what1 + what2
|
||||
|
||||
@method()
|
||||
def EchoThree(self, what1: "s", what2: "s", what3: "s") -> "sss":
|
||||
return [what1, what2, what3]
|
||||
|
||||
@method()
|
||||
def ThrowsError(self):
|
||||
raise DBusError("test.error", "something went wrong")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aio_proxy_object():
|
||||
bus_name = "aio.client.test.methods"
|
||||
|
||||
bus = await aio.MessageBus().connect()
|
||||
bus2 = await aio.MessageBus().connect()
|
||||
await bus.request_name(bus_name)
|
||||
service_interface = ExampleInterface()
|
||||
bus.export("/test/path", service_interface)
|
||||
# add some more to test nodes
|
||||
bus.export("/test/path/child1", ExampleInterface())
|
||||
bus.export("/test/path/child2", ExampleInterface())
|
||||
|
||||
introspection = await bus2.introspect(bus_name, "/test/path")
|
||||
assert type(introspection) is intr.Node
|
||||
obj = bus2.get_proxy_object(bus_name, "/test/path", introspection)
|
||||
interface = obj.get_interface(service_interface.name)
|
||||
|
||||
children = obj.get_children()
|
||||
assert len(children) == 2
|
||||
for child in obj.get_children():
|
||||
assert type(child) is aio.ProxyObject
|
||||
|
||||
result = await interface.call_ping()
|
||||
assert result is None
|
||||
|
||||
result = await interface.call_echo_string("hello")
|
||||
assert result == "hello"
|
||||
|
||||
result = await interface.call_concat_strings("hello ", "world")
|
||||
assert result == "hello world"
|
||||
|
||||
result = await interface.call_echo_three("hello", "there", "world")
|
||||
assert result == ["hello", "there", "world"]
|
||||
|
||||
result = await interface.call_echo_int64(-10000)
|
||||
assert result == -10000
|
||||
|
||||
result = await interface.call_echo_string(
|
||||
"no reply", flags=MessageFlag.NO_REPLY_EXPECTED
|
||||
)
|
||||
assert result is None
|
||||
|
||||
with pytest.raises(DBusError):
|
||||
try:
|
||||
await interface.call_throws_error()
|
||||
except DBusError as e:
|
||||
assert e.reply is not None
|
||||
assert e.type == "test.error"
|
||||
assert e.text == "something went wrong"
|
||||
raise e
|
||||
|
||||
bus.disconnect()
|
||||
bus2.disconnect()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_gi, reason=skip_reason_no_gi)
|
||||
def test_glib_proxy_object():
|
||||
bus_name = "glib.client.test.methods"
|
||||
bus = glib.MessageBus().connect_sync()
|
||||
bus.request_name_sync(bus_name)
|
||||
service_interface = ExampleInterface()
|
||||
bus.export("/test/path", service_interface)
|
||||
|
||||
bus2 = glib.MessageBus().connect_sync()
|
||||
introspection = bus2.introspect_sync(bus_name, "/test/path")
|
||||
assert type(introspection) is intr.Node
|
||||
obj = bus.get_proxy_object(bus_name, "/test/path", introspection)
|
||||
interface = obj.get_interface(service_interface.name)
|
||||
|
||||
result = interface.call_ping_sync()
|
||||
assert result is None
|
||||
|
||||
result = interface.call_echo_string_sync("hello")
|
||||
assert result == "hello"
|
||||
|
||||
result = interface.call_concat_strings_sync("hello ", "world")
|
||||
assert result == "hello world"
|
||||
|
||||
result = interface.call_echo_three_sync("hello", "there", "world")
|
||||
assert result == ["hello", "there", "world"]
|
||||
|
||||
with pytest.raises(DBusError):
|
||||
try:
|
||||
result = interface.call_throws_error_sync()
|
||||
assert False, result
|
||||
except DBusError as e:
|
||||
assert e.reply is not None
|
||||
assert e.type == "test.error"
|
||||
assert e.text == "something went wrong"
|
||||
raise e
|
||||
|
||||
bus.disconnect()
|
||||
bus2.disconnect()
|
||||
124
tests/client/test_properties.py
Normal file
124
tests/client/test_properties.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from test.util import check_gi_repository, skip_reason_no_gi
|
||||
|
||||
import pytest
|
||||
from dbus_next import DBusError, Message, aio, glib
|
||||
from dbus_next.service import PropertyAccess, ServiceInterface, dbus_property
|
||||
|
||||
has_gi = check_gi_repository()
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self):
|
||||
super().__init__("test.interface")
|
||||
self._some_property = "foo"
|
||||
self.error_name = "test.error"
|
||||
self.error_text = "i am bad"
|
||||
self._int64_property = -10000
|
||||
|
||||
@dbus_property()
|
||||
def SomeProperty(self) -> "s":
|
||||
return self._some_property
|
||||
|
||||
@SomeProperty.setter
|
||||
def SomeProperty(self, val: "s"):
|
||||
self._some_property = val
|
||||
|
||||
@dbus_property(access=PropertyAccess.READ)
|
||||
def Int64Property(self) -> "x":
|
||||
return self._int64_property
|
||||
|
||||
@dbus_property()
|
||||
def ErrorThrowingProperty(self) -> "s":
|
||||
raise DBusError(self.error_name, self.error_text)
|
||||
|
||||
@ErrorThrowingProperty.setter
|
||||
def ErrorThrowingProperty(self, val: "s"):
|
||||
raise DBusError(self.error_name, self.error_text)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aio_properties():
|
||||
service_bus = await aio.MessageBus().connect()
|
||||
service_interface = ExampleInterface()
|
||||
service_bus.export("/test/path", service_interface)
|
||||
|
||||
bus = await aio.MessageBus().connect()
|
||||
obj = bus.get_proxy_object(
|
||||
service_bus.unique_name,
|
||||
"/test/path",
|
||||
service_bus._introspect_export_path("/test/path"),
|
||||
)
|
||||
interface = obj.get_interface(service_interface.name)
|
||||
|
||||
prop = await interface.get_some_property()
|
||||
assert prop == service_interface._some_property
|
||||
|
||||
prop = await interface.get_int64_property()
|
||||
assert prop == service_interface._int64_property
|
||||
|
||||
await interface.set_some_property("different")
|
||||
assert service_interface._some_property == "different"
|
||||
|
||||
with pytest.raises(DBusError):
|
||||
try:
|
||||
prop = await interface.get_error_throwing_property()
|
||||
assert False, prop
|
||||
except DBusError as e:
|
||||
assert e.type == service_interface.error_name
|
||||
assert e.text == service_interface.error_text
|
||||
assert type(e.reply) is Message
|
||||
raise e
|
||||
|
||||
with pytest.raises(DBusError):
|
||||
try:
|
||||
await interface.set_error_throwing_property("different")
|
||||
except DBusError as e:
|
||||
assert e.type == service_interface.error_name
|
||||
assert e.text == service_interface.error_text
|
||||
assert type(e.reply) is Message
|
||||
raise e
|
||||
|
||||
service_bus.disconnect()
|
||||
bus.disconnect()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_gi, reason=skip_reason_no_gi)
|
||||
def test_glib_properties():
|
||||
service_bus = glib.MessageBus().connect_sync()
|
||||
service_interface = ExampleInterface()
|
||||
service_bus.export("/test/path", service_interface)
|
||||
|
||||
bus = glib.MessageBus().connect_sync()
|
||||
obj = bus.get_proxy_object(
|
||||
service_bus.unique_name,
|
||||
"/test/path",
|
||||
service_bus._introspect_export_path("/test/path"),
|
||||
)
|
||||
interface = obj.get_interface(service_interface.name)
|
||||
|
||||
prop = interface.get_some_property_sync()
|
||||
assert prop == service_interface._some_property
|
||||
|
||||
interface.set_some_property_sync("different")
|
||||
assert service_interface._some_property == "different"
|
||||
|
||||
with pytest.raises(DBusError):
|
||||
try:
|
||||
prop = interface.get_error_throwing_property_sync()
|
||||
assert False, prop
|
||||
except DBusError as e:
|
||||
assert e.type == service_interface.error_name
|
||||
assert e.text == service_interface.error_text
|
||||
assert type(e.reply) is Message
|
||||
raise e
|
||||
|
||||
with pytest.raises(DBusError):
|
||||
try:
|
||||
interface.set_error_throwing_property_sync("different2")
|
||||
except DBusError as e:
|
||||
assert e.type == service_interface.error_name
|
||||
assert e.text == service_interface.error_text
|
||||
assert type(e.reply) is Message
|
||||
raise e
|
||||
|
||||
service_bus.disconnect()
|
||||
227
tests/client/test_signals.py
Normal file
227
tests/client/test_signals.py
Normal file
@@ -0,0 +1,227 @@
|
||||
import pytest
|
||||
from dbus_next import Message
|
||||
from dbus_next.aio import MessageBus
|
||||
from dbus_next.constants import RequestNameReply
|
||||
from dbus_next.introspection import Node
|
||||
from dbus_next.service import ServiceInterface, signal
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self):
|
||||
super().__init__("test.interface")
|
||||
|
||||
@signal()
|
||||
def SomeSignal(self) -> "s":
|
||||
return "hello"
|
||||
|
||||
@signal()
|
||||
def SignalMultiple(self) -> "ss":
|
||||
return ["hello", "world"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_signals():
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
bus_intr = await bus1.introspect("org.freedesktop.DBus", "/org/freedesktop/DBus")
|
||||
bus_obj = bus1.get_proxy_object(
|
||||
"org.freedesktop.DBus", "/org/freedesktop/DBus", bus_intr
|
||||
)
|
||||
stats = bus_obj.get_interface("org.freedesktop.DBus.Debug.Stats")
|
||||
|
||||
await bus1.request_name("test.signals.name")
|
||||
service_interface = ExampleInterface()
|
||||
bus1.export("/test/path", service_interface)
|
||||
|
||||
obj = bus2.get_proxy_object(
|
||||
"test.signals.name", "/test/path", bus1._introspect_export_path("/test/path")
|
||||
)
|
||||
interface = obj.get_interface(service_interface.name)
|
||||
|
||||
async def ping():
|
||||
await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
interface="org.freedesktop.DBus.Peer",
|
||||
path="/test/path",
|
||||
member="Ping",
|
||||
)
|
||||
)
|
||||
|
||||
err = None
|
||||
|
||||
single_counter = 0
|
||||
|
||||
def single_handler(value):
|
||||
try:
|
||||
nonlocal single_counter
|
||||
nonlocal err
|
||||
assert value == "hello"
|
||||
single_counter += 1
|
||||
except Exception as e:
|
||||
err = e
|
||||
|
||||
multiple_counter = 0
|
||||
|
||||
def multiple_handler(value1, value2):
|
||||
nonlocal multiple_counter
|
||||
nonlocal err
|
||||
try:
|
||||
assert value1 == "hello"
|
||||
assert value2 == "world"
|
||||
multiple_counter += 1
|
||||
except Exception as e:
|
||||
err = e
|
||||
|
||||
await ping()
|
||||
match_rules = await stats.call_get_all_match_rules()
|
||||
assert bus2.unique_name in match_rules
|
||||
bus_match_rules = match_rules[bus2.unique_name]
|
||||
# the bus connection itself takes a rule on NameOwnerChange after the high
|
||||
# level client is initialized
|
||||
assert len(bus_match_rules) == 1
|
||||
assert len(bus2._user_message_handlers) == 0
|
||||
|
||||
interface.on_some_signal(single_handler)
|
||||
interface.on_signal_multiple(multiple_handler)
|
||||
|
||||
# Interlude: adding a signal handler with `on_[signal]` should add a match rule and
|
||||
# message handler. Removing a signal handler with `off_[signal]` should
|
||||
# remove the match rule and message handler to avoid memory leaks.
|
||||
await ping()
|
||||
match_rules = await stats.call_get_all_match_rules()
|
||||
assert bus2.unique_name in match_rules
|
||||
bus_match_rules = match_rules[bus2.unique_name]
|
||||
# test the match rule and user handler has been added
|
||||
assert len(bus_match_rules) == 2
|
||||
assert (
|
||||
"type='signal',interface='test.interface',path='/test/path',sender='test.signals.name'"
|
||||
in bus_match_rules
|
||||
)
|
||||
assert len(bus2._user_message_handlers) == 1
|
||||
|
||||
service_interface.SomeSignal()
|
||||
await ping()
|
||||
assert err is None
|
||||
assert single_counter == 1
|
||||
|
||||
service_interface.SignalMultiple()
|
||||
await ping()
|
||||
assert err is None
|
||||
assert multiple_counter == 1
|
||||
|
||||
# special case: another bus with the same path and interface but on a
|
||||
# different name and connection will trigger the match rule of the first
|
||||
# (happens with mpris)
|
||||
bus3 = await MessageBus().connect()
|
||||
await bus3.request_name("test.signals.name2")
|
||||
service_interface2 = ExampleInterface()
|
||||
bus3.export("/test/path", service_interface2)
|
||||
|
||||
obj = bus2.get_proxy_object(
|
||||
"test.signals.name2", "/test/path", bus3._introspect_export_path("/test/path")
|
||||
)
|
||||
# we have to add a dummy handler to add the match rule
|
||||
iface2 = obj.get_interface(service_interface2.name)
|
||||
|
||||
def dummy_signal_handler(what):
|
||||
pass
|
||||
|
||||
iface2.on_some_signal(dummy_signal_handler)
|
||||
await ping()
|
||||
|
||||
service_interface2.SomeSignal()
|
||||
await ping()
|
||||
# single_counter is not incremented for signals of the second interface
|
||||
assert single_counter == 1
|
||||
|
||||
interface.off_some_signal(single_handler)
|
||||
interface.off_signal_multiple(multiple_handler)
|
||||
iface2.off_some_signal(dummy_signal_handler)
|
||||
|
||||
# After `off_[signal]`, the match rule and user handler should be removed
|
||||
await ping()
|
||||
match_rules = await stats.call_get_all_match_rules()
|
||||
assert bus2.unique_name in match_rules
|
||||
bus_match_rules = match_rules[bus2.unique_name]
|
||||
assert len(bus_match_rules) == 1
|
||||
assert (
|
||||
"type='signal',interface='test.interface',path='/test/path',sender='test.signals.name'"
|
||||
not in bus_match_rules
|
||||
)
|
||||
assert len(bus2._user_message_handlers) == 0
|
||||
|
||||
bus1.disconnect()
|
||||
bus2.disconnect()
|
||||
bus3.disconnect()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_signals_with_changing_owners():
|
||||
well_known_name = "test.signals.changing.name"
|
||||
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
bus3 = await MessageBus().connect()
|
||||
|
||||
async def ping():
|
||||
await bus1.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
interface="org.freedesktop.DBus.Peer",
|
||||
path="/test/path",
|
||||
member="Ping",
|
||||
)
|
||||
)
|
||||
|
||||
service_interface = ExampleInterface()
|
||||
introspection = Node.default()
|
||||
introspection.interfaces.append(service_interface.introspect())
|
||||
|
||||
# get the interface before export
|
||||
obj = bus1.get_proxy_object(well_known_name, "/test/path", introspection)
|
||||
iface = obj.get_interface("test.interface")
|
||||
counter = 0
|
||||
|
||||
def handler(what):
|
||||
nonlocal counter
|
||||
counter += 1
|
||||
|
||||
iface.on_some_signal(handler)
|
||||
await ping()
|
||||
|
||||
# now export and get the name
|
||||
bus2.export("/test/path", service_interface)
|
||||
result = await bus2.request_name(well_known_name)
|
||||
assert result is RequestNameReply.PRIMARY_OWNER
|
||||
|
||||
# the signal should work
|
||||
service_interface.SomeSignal()
|
||||
await ping()
|
||||
assert counter == 1
|
||||
counter = 0
|
||||
|
||||
# now queue up a transfer of the name
|
||||
service_interface2 = ExampleInterface()
|
||||
bus3.export("/test/path", service_interface2)
|
||||
result = await bus3.request_name(well_known_name)
|
||||
assert result is RequestNameReply.IN_QUEUE
|
||||
|
||||
# if it doesn't own the name, the signal shouldn't work here
|
||||
service_interface2.SomeSignal()
|
||||
await ping()
|
||||
assert counter == 0
|
||||
|
||||
# now transfer over the name and it should work
|
||||
bus2.disconnect()
|
||||
await ping()
|
||||
|
||||
service_interface2.SomeSignal()
|
||||
await ping()
|
||||
assert counter == 1
|
||||
counter = 0
|
||||
|
||||
bus1.disconnect()
|
||||
bus2.disconnect()
|
||||
bus3.disconnect()
|
||||
29
tests/data/introspection.xml
Normal file
29
tests/data/introspection.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node name="/com/example/sample_object0">
|
||||
<interface name="com.example.SampleInterface0">
|
||||
<method name="Frobate">
|
||||
<arg name="foo" type="i" direction="in"/>
|
||||
<arg name="bar" type="s" direction="out"/>
|
||||
<arg name="baz" type="a{us}" direction="out"/>
|
||||
<annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
|
||||
</method>
|
||||
<method name="Bazify">
|
||||
<arg name="bar" type="(iiu)" direction="in"/>
|
||||
<arg name="bar" type="v" direction="out"/>
|
||||
</method>
|
||||
<method name="Mogrify">
|
||||
<arg name="bar" type="(iiav)" direction="in"/>
|
||||
</method>
|
||||
<signal name="Changed">
|
||||
<arg name="new_value" type="b"/>
|
||||
</signal>
|
||||
<signal name="ChangedMulti">
|
||||
<arg name="new_value1" type="b"/>
|
||||
<arg name="new_value2" type="y"/>
|
||||
</signal>
|
||||
<property name="Bar" type="y" access="write"/>
|
||||
</interface>
|
||||
<node name="child_of_sample_object"/>
|
||||
<node name="another_child_of_sample_object"/>
|
||||
</node>
|
||||
234
tests/data/messages.json
Normal file
234
tests/data/messages.json
Normal file
@@ -0,0 +1,234 @@
|
||||
[
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": ""
|
||||
},
|
||||
"data": "6c01000100000000010000006d00000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e4442757300000000"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "as",
|
||||
"body": [["hello", "world"]]
|
||||
},
|
||||
"data": "6c0100011a000000010000007800000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e44427573000000000801670002617300160000000500000068656c6c6f00000005000000776f726c6400"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "a(uu)",
|
||||
"body": [
|
||||
[
|
||||
[1, 1],
|
||||
[2, 2]
|
||||
]
|
||||
]
|
||||
},
|
||||
"data": "6c01000118000000010000007b00000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e444275730000000008016700056128757529000000000000100000000000000001000000010000000200000002000000"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "a{ss}",
|
||||
"body": [
|
||||
{
|
||||
"foo": "bar",
|
||||
"bat": "baz"
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": "6c01000128000000010000007b00000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e44427573000000000801670005617b73737d000000000000200000000000000003000000666f6f00030000006261720003000000626174000300000062617a00"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "a(as(uu(a{ss})))",
|
||||
"body": [
|
||||
[
|
||||
[
|
||||
["hello", "there"],
|
||||
[
|
||||
5,
|
||||
6,
|
||||
[
|
||||
{
|
||||
"five": "six",
|
||||
"seven": "eight"
|
||||
}
|
||||
]
|
||||
]
|
||||
],
|
||||
[
|
||||
["to", "the", "world"],
|
||||
[
|
||||
7,
|
||||
8,
|
||||
[
|
||||
{
|
||||
"seven": "eight",
|
||||
"nine": "ten"
|
||||
}
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"data": "6c010001c4000000010000008600000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e444275730000000008016700106128617328757528617b73737d292929000000bc00000000000000160000000500000068656c6c6f0000000500000074686572650000000000000005000000060000002e0000000000000004000000666976650000000003000000736978000000000005000000736576656e0000000500000065696768740000001a00000002000000746f0000030000007468650005000000776f726c6400000007000000080000002c0000000000000005000000736576656e000000050000006569676874000000040000006e696e65000000000300000074656e00"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "t",
|
||||
"body": [9007199254740988]
|
||||
},
|
||||
"data": "6c01000108000000010000007700000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e44427573000000000801670001740000fcffffffffff1f00"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "x",
|
||||
"body": [-9007199254740988]
|
||||
},
|
||||
"data": "6c01000108000000010000007700000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e44427573000000000801670001780000040000000000e0ff"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "bnqiud",
|
||||
"body": [true, -200, 150, -20000, 20000, 9083492084.4444]
|
||||
},
|
||||
"data": "6c01000118000000010000007c00000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e44427573000000000801670006626e7169756400000000000100000038ff9600e0b1ffff204e0000228ea3b758eb0042"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "v",
|
||||
"body": [
|
||||
{
|
||||
"signature": "s",
|
||||
"value": "hello world"
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": "6c01000114000000010000007700000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e44427573000000000801670001760000017300000b00000068656c6c6f20776f726c6400"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "v",
|
||||
"body": [
|
||||
{
|
||||
"signature": "v",
|
||||
"value": {
|
||||
"signature": "s",
|
||||
"value": "hello"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": "6c01000112000000010000007700000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e4442757300000000080167000176000001760001730000000500000068656c6c6f00"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "a{sv}",
|
||||
"body": [
|
||||
{
|
||||
"variant_key_1": {
|
||||
"signature": "s",
|
||||
"value": "variant_val_1"
|
||||
},
|
||||
"variant_key_2": {
|
||||
"signature": "s",
|
||||
"value": "variant_val_2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": "6c01000162000000010000007b00000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e44427573000000000801670005617b73767d0000000000005a000000000000000d00000076617269616e745f6b65795f31000173000000000d00000076617269616e745f76616c5f31000000000000000d00000076617269616e745f6b65795f32000173000000000d00000076617269616e745f76616c5f3200"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "v",
|
||||
"body": [
|
||||
{
|
||||
"signature": "as",
|
||||
"value": ["foo", "bar"]
|
||||
}
|
||||
]
|
||||
},
|
||||
"data": "6c01000118000000010000007700000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e44427573000000000801670001760000026173001000000003000000666f6f000300000062617200"
|
||||
},
|
||||
{
|
||||
"message": {
|
||||
"destination": "org.freedesktop.DBus",
|
||||
"path": "/org/freedesktop/DBus",
|
||||
"interface": "org.freedesktop.DBus",
|
||||
"member": "Hello",
|
||||
"serial": 1,
|
||||
"signature": "vas",
|
||||
"body": [
|
||||
{
|
||||
"signature": "v",
|
||||
"value": {
|
||||
"signature": "s",
|
||||
"value": "world"
|
||||
}
|
||||
},
|
||||
["bar"]
|
||||
]
|
||||
},
|
||||
"data": "6c01000120000000010000007900000001016f00150000002f6f72672f667265656465736b746f702f4442757300000002017300140000006f72672e667265656465736b746f702e4442757300000000030173000500000048656c6c6f00000006017300140000006f72672e667265656465736b746f702e444275730000000008016700037661730000000000000000017600017300000005000000776f726c64000000080000000300000062617200"
|
||||
}
|
||||
]
|
||||
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 == [{}]
|
||||
28
tests/test_address_parser.py
Normal file
28
tests/test_address_parser.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from dbus_next._private.address import parse_address
|
||||
|
||||
|
||||
def test_valid_addresses():
|
||||
|
||||
valid_addresses = {
|
||||
"unix:path=/run/user/1000/bus": [("unix", {"path": "/run/user/1000/bus"})],
|
||||
"unix:abstract=/tmp/dbus-ft9sODWpZk,guid=a7b1d5912379c2d471165e9b5cb74a03": [
|
||||
(
|
||||
"unix",
|
||||
{
|
||||
"abstract": "/tmp/dbus-ft9sODWpZk",
|
||||
"guid": "a7b1d5912379c2d471165e9b5cb74a03",
|
||||
},
|
||||
)
|
||||
],
|
||||
"unix1:key1=val1;unix2:key2=val2": [
|
||||
("unix1", {"key1": "val1"}),
|
||||
("unix2", {"key2": "val2"}),
|
||||
],
|
||||
"unix:escaped=hello%20world": [("unix", {"escaped": "hello world"})],
|
||||
"tcp:host=127.0.0.1,port=55556": [
|
||||
("tcp", {"host": "127.0.0.1", "port": "55556"})
|
||||
],
|
||||
}
|
||||
|
||||
for address, parsed in valid_addresses.items():
|
||||
assert parse_address(address) == parsed
|
||||
146
tests/test_aio_low_level.py
Normal file
146
tests/test_aio_low_level.py
Normal file
@@ -0,0 +1,146 @@
|
||||
import pytest
|
||||
from dbus_next import Message, MessageFlag, MessageType
|
||||
from dbus_next.aio import MessageBus
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_standard_interfaces():
|
||||
bus = await MessageBus().connect()
|
||||
msg = Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="ListNames",
|
||||
serial=bus.next_serial(),
|
||||
)
|
||||
reply = await bus.call(msg)
|
||||
|
||||
assert reply.message_type == MessageType.METHOD_RETURN
|
||||
assert reply.reply_serial == msg.serial
|
||||
assert reply.signature == "as"
|
||||
assert bus.unique_name in reply.body[0]
|
||||
|
||||
msg.interface = "org.freedesktop.DBus.Introspectable"
|
||||
msg.member = "Introspect"
|
||||
msg.serial = bus.next_serial()
|
||||
|
||||
reply = await bus.call(msg)
|
||||
assert reply.message_type == MessageType.METHOD_RETURN
|
||||
assert reply.reply_serial == msg.serial
|
||||
assert reply.signature == "s"
|
||||
assert type(reply.body[0]) is str
|
||||
|
||||
msg.member = "MemberDoesNotExist"
|
||||
msg.serial = bus.next_serial()
|
||||
|
||||
reply = await bus.call(msg)
|
||||
assert reply.message_type == MessageType.ERROR
|
||||
assert reply.reply_serial == msg.serial
|
||||
assert reply.error_name
|
||||
assert reply.signature == "s"
|
||||
assert type(reply.body[0]) is str
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sending_messages_between_buses():
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
msg = Message(
|
||||
destination=bus1.unique_name,
|
||||
path="/org/test/path",
|
||||
interface="org.test.iface",
|
||||
member="SomeMember",
|
||||
serial=bus2.next_serial(),
|
||||
)
|
||||
|
||||
def message_handler(sent):
|
||||
if sent.sender == bus2.unique_name and sent.serial == msg.serial:
|
||||
assert sent.path == msg.path
|
||||
assert sent.serial == msg.serial
|
||||
assert sent.interface == msg.interface
|
||||
assert sent.member == msg.member
|
||||
bus1.send(Message.new_method_return(sent, "s", ["got it"]))
|
||||
bus1.remove_message_handler(message_handler)
|
||||
return True
|
||||
|
||||
bus1.add_message_handler(message_handler)
|
||||
|
||||
reply = await bus2.call(msg)
|
||||
|
||||
assert reply.message_type == MessageType.METHOD_RETURN
|
||||
assert reply.sender == bus1.unique_name
|
||||
assert reply.signature == "s"
|
||||
assert reply.body == ["got it"]
|
||||
assert reply.reply_serial == msg.serial
|
||||
|
||||
def message_handler_error(sent):
|
||||
if sent.sender == bus2.unique_name and sent.serial == msg.serial:
|
||||
assert sent.path == msg.path
|
||||
assert sent.serial == msg.serial
|
||||
assert sent.interface == msg.interface
|
||||
assert sent.member == msg.member
|
||||
bus1.send(Message.new_error(sent, "org.test.Error", "throwing an error"))
|
||||
bus1.remove_message_handler(message_handler_error)
|
||||
return True
|
||||
|
||||
bus1.add_message_handler(message_handler_error)
|
||||
|
||||
msg.serial = bus2.next_serial()
|
||||
|
||||
reply = await bus2.call(msg)
|
||||
|
||||
assert reply.message_type == MessageType.ERROR
|
||||
assert reply.sender == bus1.unique_name
|
||||
assert reply.reply_serial == msg.serial
|
||||
assert reply.error_name == "org.test.Error"
|
||||
assert reply.signature == "s"
|
||||
assert reply.body == ["throwing an error"]
|
||||
|
||||
msg.serial = bus2.next_serial()
|
||||
msg.flags = MessageFlag.NO_REPLY_EXPECTED
|
||||
reply = await bus2.call(msg)
|
||||
assert reply is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sending_signals_between_buses(event_loop):
|
||||
bus1 = await MessageBus().connect()
|
||||
bus2 = await MessageBus().connect()
|
||||
|
||||
add_match_msg = Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="AddMatch",
|
||||
signature="s",
|
||||
body=[f"sender={bus2.unique_name}"],
|
||||
)
|
||||
|
||||
await bus1.call(add_match_msg)
|
||||
|
||||
async def wait_for_message():
|
||||
future = event_loop.create_future()
|
||||
|
||||
def message_handler(signal):
|
||||
if signal.sender == bus2.unique_name:
|
||||
bus1.remove_message_handler(message_handler)
|
||||
future.set_result(signal)
|
||||
|
||||
bus1.add_message_handler(message_handler)
|
||||
return await future
|
||||
|
||||
bus2.send(
|
||||
Message.new_signal(
|
||||
"/org/test/path", "org.test.interface", "SomeSignal", "s", ["a signal"]
|
||||
)
|
||||
)
|
||||
|
||||
signal = await wait_for_message()
|
||||
|
||||
assert signal.message_type == MessageType.SIGNAL
|
||||
assert signal.path == "/org/test/path"
|
||||
assert signal.interface == "org.test.interface"
|
||||
assert signal.member == "SomeSignal"
|
||||
assert signal.signature == "s"
|
||||
assert signal.body == ["a signal"]
|
||||
64
tests/test_big_message.py
Normal file
64
tests/test_big_message.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from test.util import check_gi_repository, skip_reason_no_gi
|
||||
|
||||
import pytest
|
||||
from dbus_next import Message, MessageType, aio, glib
|
||||
from dbus_next.service import ServiceInterface, method
|
||||
|
||||
has_gi = check_gi_repository()
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self):
|
||||
super().__init__("example.interface")
|
||||
|
||||
@method()
|
||||
def echo_bytes(self, what: "ay") -> "ay":
|
||||
return what
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_aio_big_message():
|
||||
"this tests that nonblocking reads and writes actually work for aio"
|
||||
bus1 = await aio.MessageBus().connect()
|
||||
bus2 = await aio.MessageBus().connect()
|
||||
interface = ExampleInterface()
|
||||
bus1.export("/test/path", interface)
|
||||
|
||||
# two megabytes
|
||||
big_body = [bytes(1000000) * 2]
|
||||
result = await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path="/test/path",
|
||||
interface=interface.name,
|
||||
member="echo_bytes",
|
||||
signature="ay",
|
||||
body=big_body,
|
||||
)
|
||||
)
|
||||
assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
|
||||
assert result.body[0] == big_body[0]
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_gi, reason=skip_reason_no_gi)
|
||||
def test_glib_big_message():
|
||||
"this tests that nonblocking reads and writes actually work for glib"
|
||||
bus1 = glib.MessageBus().connect_sync()
|
||||
bus2 = glib.MessageBus().connect_sync()
|
||||
interface = ExampleInterface()
|
||||
bus1.export("/test/path", interface)
|
||||
|
||||
# two megabytes
|
||||
big_body = [bytes(1000000) * 2]
|
||||
result = bus2.call_sync(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path="/test/path",
|
||||
interface=interface.name,
|
||||
member="echo_bytes",
|
||||
signature="ay",
|
||||
body=big_body,
|
||||
)
|
||||
)
|
||||
assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
|
||||
assert result.body[0] == big_body[0]
|
||||
62
tests/test_disconnect.py
Normal file
62
tests/test_disconnect.py
Normal file
@@ -0,0 +1,62 @@
|
||||
import functools
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from dbus_next import Message
|
||||
from dbus_next.aio import MessageBus
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_bus_disconnect_before_reply(event_loop):
|
||||
"""In this test, the bus disconnects before the reply comes in. Make sure
|
||||
the caller receives a reply with the error instead of hanging."""
|
||||
bus = MessageBus()
|
||||
assert not bus.connected
|
||||
await bus.connect()
|
||||
assert bus.connected
|
||||
|
||||
ping = bus.call(
|
||||
Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="Ping",
|
||||
)
|
||||
)
|
||||
|
||||
event_loop.call_soon(bus.disconnect)
|
||||
|
||||
with pytest.raises((EOFError, BrokenPipeError)):
|
||||
await ping
|
||||
|
||||
assert bus._disconnected
|
||||
assert not bus.connected
|
||||
assert (await bus.wait_for_disconnect()) is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unexpected_disconnect(event_loop):
|
||||
bus = MessageBus()
|
||||
assert not bus.connected
|
||||
await bus.connect()
|
||||
assert bus.connected
|
||||
|
||||
ping = bus.call(
|
||||
Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="Ping",
|
||||
)
|
||||
)
|
||||
|
||||
event_loop.call_soon(functools.partial(os.close, bus._fd))
|
||||
|
||||
with pytest.raises(OSError):
|
||||
await ping
|
||||
|
||||
assert bus._disconnected
|
||||
assert not bus.connected
|
||||
|
||||
with pytest.raises(OSError):
|
||||
await bus.wait_for_disconnect()
|
||||
371
tests/test_fd_passing.py
Normal file
371
tests/test_fd_passing.py
Normal file
@@ -0,0 +1,371 @@
|
||||
"""This tests the ability to send and receive file descriptors in dbus messages"""
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from dbus_next import Message, MessageType
|
||||
from dbus_next.aio import MessageBus
|
||||
from dbus_next.service import ServiceInterface, dbus_property, method, signal
|
||||
from dbus_next.signature import SignatureTree, Variant
|
||||
|
||||
|
||||
def open_file():
|
||||
return os.open(os.devnull, os.O_RDONLY)
|
||||
|
||||
|
||||
class ExampleInterface(ServiceInterface):
|
||||
def __init__(self, name):
|
||||
super().__init__(name)
|
||||
self.fds = []
|
||||
|
||||
@method()
|
||||
def ReturnsFd(self) -> "h":
|
||||
fd = open_file()
|
||||
self.fds.append(fd)
|
||||
return fd
|
||||
|
||||
@method()
|
||||
def AcceptsFd(self, fd: "h"):
|
||||
assert fd != 0
|
||||
self.fds.append(fd)
|
||||
|
||||
def get_last_fd(self):
|
||||
return self.fds[-1]
|
||||
|
||||
def cleanup(self):
|
||||
for fd in self.fds:
|
||||
os.close(fd)
|
||||
self.fds.clear()
|
||||
|
||||
@signal()
|
||||
def SignalFd(self) -> "h":
|
||||
fd = open_file()
|
||||
self.fds.append(fd)
|
||||
return fd
|
||||
|
||||
@dbus_property()
|
||||
def PropFd(self) -> "h":
|
||||
if not self.fds:
|
||||
fd = open_file()
|
||||
self.fds.append(fd)
|
||||
return self.fds[-1]
|
||||
|
||||
@PropFd.setter
|
||||
def PropFd(self, fd: "h"):
|
||||
assert fd
|
||||
self.fds.append(fd)
|
||||
|
||||
|
||||
def assert_fds_equal(fd1, fd2):
|
||||
assert fd1
|
||||
assert fd2
|
||||
|
||||
stat1 = os.fstat(fd1)
|
||||
stat2 = os.fstat(fd2)
|
||||
|
||||
assert stat1.st_dev == stat2.st_dev
|
||||
assert stat1.st_ino == stat2.st_ino
|
||||
assert stat1.st_rdev == stat2.st_rdev
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sending_file_descriptor_low_level():
|
||||
bus1 = await MessageBus(negotiate_unix_fd=True).connect()
|
||||
bus2 = await MessageBus(negotiate_unix_fd=True).connect()
|
||||
|
||||
fd_before = open_file()
|
||||
fd_after = None
|
||||
|
||||
msg = Message(
|
||||
destination=bus1.unique_name,
|
||||
path="/org/test/path",
|
||||
interface="org.test.iface",
|
||||
member="SomeMember",
|
||||
body=[0],
|
||||
signature="h",
|
||||
unix_fds=[fd_before],
|
||||
)
|
||||
|
||||
def message_handler(sent):
|
||||
nonlocal fd_after
|
||||
if sent.sender == bus2.unique_name and sent.serial == msg.serial:
|
||||
assert sent.path == msg.path
|
||||
assert sent.serial == msg.serial
|
||||
assert sent.interface == msg.interface
|
||||
assert sent.member == msg.member
|
||||
assert sent.body == [0]
|
||||
assert len(sent.unix_fds) == 1
|
||||
fd_after = sent.unix_fds[0]
|
||||
bus1.send(Message.new_method_return(sent, "s", ["got it"]))
|
||||
bus1.remove_message_handler(message_handler)
|
||||
return True
|
||||
|
||||
bus1.add_message_handler(message_handler)
|
||||
|
||||
reply = await bus2.call(msg)
|
||||
assert reply.body == ["got it"]
|
||||
assert fd_after is not None
|
||||
|
||||
assert_fds_equal(fd_before, fd_after)
|
||||
|
||||
for fd in [fd_before, fd_after]:
|
||||
os.close(fd)
|
||||
for bus in [bus1, bus2]:
|
||||
bus.disconnect()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_high_level_service_fd_passing(event_loop):
|
||||
bus1 = await MessageBus(negotiate_unix_fd=True).connect()
|
||||
bus2 = await MessageBus(negotiate_unix_fd=True).connect()
|
||||
|
||||
interface_name = "test.interface"
|
||||
interface = ExampleInterface(interface_name)
|
||||
export_path = "/test/path"
|
||||
|
||||
async def call(member, signature="", body=[], unix_fds=[], iface=interface.name):
|
||||
return await bus2.call(
|
||||
Message(
|
||||
destination=bus1.unique_name,
|
||||
path=export_path,
|
||||
interface=iface,
|
||||
member=member,
|
||||
signature=signature,
|
||||
body=body,
|
||||
unix_fds=unix_fds,
|
||||
)
|
||||
)
|
||||
|
||||
bus1.export(export_path, interface)
|
||||
|
||||
# test that an fd can be returned by the service
|
||||
reply = await call("ReturnsFd")
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body
|
||||
assert reply.signature == "h"
|
||||
assert len(reply.unix_fds) == 1
|
||||
assert_fds_equal(interface.get_last_fd(), reply.unix_fds[0])
|
||||
interface.cleanup()
|
||||
os.close(reply.unix_fds[0])
|
||||
|
||||
# test that an fd can be sent to the service
|
||||
fd = open_file()
|
||||
reply = await call("AcceptsFd", signature="h", body=[0], unix_fds=[fd])
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body
|
||||
assert_fds_equal(interface.get_last_fd(), fd)
|
||||
|
||||
interface.cleanup()
|
||||
os.close(fd)
|
||||
|
||||
# signals
|
||||
fut = event_loop.create_future()
|
||||
|
||||
def fd_listener(msg):
|
||||
if msg.sender == bus1.unique_name and msg.message_type == MessageType.SIGNAL:
|
||||
fut.set_result(msg)
|
||||
|
||||
reply = await bus2.call(
|
||||
Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
member="AddMatch",
|
||||
signature="s",
|
||||
body=[f"sender='{bus1.unique_name}'"],
|
||||
)
|
||||
)
|
||||
assert reply.message_type == MessageType.METHOD_RETURN
|
||||
|
||||
bus2.add_message_handler(fd_listener)
|
||||
interface.SignalFd()
|
||||
reply = await fut
|
||||
|
||||
assert len(reply.unix_fds) == 1
|
||||
assert reply.body == [0]
|
||||
assert_fds_equal(reply.unix_fds[0], interface.get_last_fd())
|
||||
|
||||
interface.cleanup()
|
||||
os.close(reply.unix_fds[0])
|
||||
|
||||
# properties
|
||||
reply = await call(
|
||||
"Get", "ss", [interface_name, "PropFd"], iface="org.freedesktop.DBus.Properties"
|
||||
)
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body
|
||||
assert reply.body[0].signature == "h"
|
||||
assert reply.body[0].value == 0
|
||||
assert len(reply.unix_fds) == 1
|
||||
assert_fds_equal(interface.get_last_fd(), reply.unix_fds[0])
|
||||
interface.cleanup()
|
||||
os.close(reply.unix_fds[0])
|
||||
|
||||
fd = open_file()
|
||||
reply = await call(
|
||||
"Set",
|
||||
"ssv",
|
||||
[interface_name, "PropFd", Variant("h", 0)],
|
||||
iface="org.freedesktop.DBus.Properties",
|
||||
unix_fds=[fd],
|
||||
)
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body
|
||||
assert_fds_equal(interface.get_last_fd(), fd)
|
||||
interface.cleanup()
|
||||
os.close(fd)
|
||||
|
||||
reply = await call(
|
||||
"GetAll", "s", [interface_name], iface="org.freedesktop.DBus.Properties"
|
||||
)
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body
|
||||
assert reply.body[0]["PropFd"].signature == "h"
|
||||
assert reply.body[0]["PropFd"].value == 0
|
||||
assert len(reply.unix_fds) == 1
|
||||
assert_fds_equal(interface.get_last_fd(), reply.unix_fds[0])
|
||||
interface.cleanup()
|
||||
os.close(reply.unix_fds[0])
|
||||
|
||||
for bus in [bus1, bus2]:
|
||||
bus.disconnect()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_sending_file_descriptor_with_proxy(event_loop):
|
||||
name = "dbus.next.test.service"
|
||||
path = "/test/path"
|
||||
interface_name = "test.interface"
|
||||
|
||||
bus = await MessageBus(negotiate_unix_fd=True).connect()
|
||||
|
||||
interface = ExampleInterface(interface_name)
|
||||
bus.export(path, interface)
|
||||
await bus.request_name(name)
|
||||
|
||||
intr = await bus.introspect(name, path)
|
||||
|
||||
proxy = bus.get_proxy_object(name, path, intr)
|
||||
proxy_interface = proxy.get_interface(interface_name)
|
||||
|
||||
# test fds are replaced correctly in all high level interfaces
|
||||
fd = await proxy_interface.call_returns_fd()
|
||||
assert_fds_equal(interface.get_last_fd(), fd)
|
||||
interface.cleanup()
|
||||
os.close(fd)
|
||||
|
||||
fd = open_file()
|
||||
await proxy_interface.call_accepts_fd(fd)
|
||||
assert_fds_equal(interface.get_last_fd(), fd)
|
||||
interface.cleanup()
|
||||
os.close(fd)
|
||||
|
||||
fd = await proxy_interface.get_prop_fd()
|
||||
assert_fds_equal(interface.get_last_fd(), fd)
|
||||
interface.cleanup()
|
||||
os.close(fd)
|
||||
|
||||
fd = open_file()
|
||||
await proxy_interface.set_prop_fd(fd)
|
||||
assert_fds_equal(interface.get_last_fd(), fd)
|
||||
interface.cleanup()
|
||||
os.close(fd)
|
||||
|
||||
fut = event_loop.create_future()
|
||||
|
||||
def on_signal_fd(fd):
|
||||
fut.set_result(fd)
|
||||
proxy_interface.off_signal_fd(on_signal_fd)
|
||||
|
||||
proxy_interface.on_signal_fd(on_signal_fd)
|
||||
interface.SignalFd()
|
||||
fd = await fut
|
||||
assert_fds_equal(interface.get_last_fd(), fd)
|
||||
interface.cleanup()
|
||||
os.close(fd)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"result, out_signature, expected",
|
||||
[
|
||||
pytest.param(5, "h", ([0], [5]), id='Signature: "h"'),
|
||||
pytest.param([5, "foo"], "hs", ([0, "foo"], [5]), id='Signature: "hs"'),
|
||||
pytest.param([5, 7], "hh", ([0, 1], [5, 7]), id='Signature: "hh"'),
|
||||
pytest.param([5, 7], "ah", ([[0, 1]], [5, 7]), id='Signature: "ah"'),
|
||||
pytest.param([9], "ah", ([[0]], [9]), id='Signature: "ah"'),
|
||||
pytest.param([3], "(h)", ([[0]], [3]), id='Signature: "(h)"'),
|
||||
pytest.param([3, "foo"], "(hs)", ([[0, "foo"]], [3]), id='Signature: "(hs)"'),
|
||||
pytest.param(
|
||||
[[7, "foo"], [8, "bar"]],
|
||||
"a(hs)",
|
||||
([[[0, "foo"], [1, "bar"]]], [7, 8]),
|
||||
id='Signature: "a(hs)"',
|
||||
),
|
||||
pytest.param({"foo": 3}, "a{sh}", ([{"foo": 0}], [3]), id='Signature: "a{sh}"'),
|
||||
pytest.param(
|
||||
{"foo": 3, "bar": 6},
|
||||
"a{sh}",
|
||||
([{"foo": 0, "bar": 1}], [3, 6]),
|
||||
id='Signature: "a{sh}"',
|
||||
),
|
||||
pytest.param(
|
||||
{"foo": [3, 8]},
|
||||
"a{sah}",
|
||||
([{"foo": [0, 1]}], [3, 8]),
|
||||
id='Signature: "a{sah}"',
|
||||
),
|
||||
pytest.param(
|
||||
{"foo": Variant("t", 100)},
|
||||
"a{sv}",
|
||||
([{"foo": Variant("t", 100)}], []),
|
||||
id='Signature: "a{sv}"',
|
||||
),
|
||||
pytest.param(
|
||||
["one", ["two", [Variant("s", "three")]]],
|
||||
"(s(s(v)))",
|
||||
([["one", ["two", [Variant("s", "three")]]]], []),
|
||||
id='Signature: "(s(s(v)))"',
|
||||
),
|
||||
pytest.param(
|
||||
Variant("h", 2), "v", ([Variant("h", 0)], [2]), id='Variant with: "h"'
|
||||
),
|
||||
pytest.param(
|
||||
Variant("(hh)", [2, 8]),
|
||||
"v",
|
||||
([Variant("(hh)", [0, 1])], [2, 8]),
|
||||
id='Variant with: "(hh)"',
|
||||
),
|
||||
pytest.param(
|
||||
Variant("ah", [2, 4]),
|
||||
"v",
|
||||
([Variant("ah", [0, 1])], [2, 4]),
|
||||
id='Variant with: "ah"',
|
||||
),
|
||||
pytest.param(
|
||||
Variant("(ss)", ["hello", "world"]),
|
||||
"v",
|
||||
([Variant("(ss)", ["hello", "world"])], []),
|
||||
id='Variant with: "(ss)"',
|
||||
),
|
||||
pytest.param(
|
||||
Variant("v", Variant("t", 100)),
|
||||
"v",
|
||||
([Variant("v", Variant("t", 100))], []),
|
||||
id='Variant with: "v"',
|
||||
),
|
||||
pytest.param(
|
||||
[
|
||||
Variant("v", Variant("(ss)", ["hello", "world"])),
|
||||
{"foo": Variant("t", 100)},
|
||||
["one", ["two", [Variant("s", "three")]]],
|
||||
],
|
||||
"va{sv}(s(s(v)))",
|
||||
(
|
||||
[
|
||||
Variant("v", Variant("(ss)", ["hello", "world"])),
|
||||
{"foo": Variant("t", 100)},
|
||||
["one", ["two", [Variant("s", "three")]]],
|
||||
],
|
||||
[],
|
||||
),
|
||||
id='Variant with: "va{sv}(s(s(v)))"',
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_fn_result_to_body(result, out_signature, expected):
|
||||
out_signature_tree = SignatureTree(out_signature)
|
||||
assert ServiceInterface._fn_result_to_body(result, out_signature_tree) == expected
|
||||
158
tests/test_glib_low_level.py
Normal file
158
tests/test_glib_low_level.py
Normal file
@@ -0,0 +1,158 @@
|
||||
from test.util import check_gi_repository, skip_reason_no_gi
|
||||
|
||||
import pytest
|
||||
from dbus_next import Message, MessageFlag, MessageType
|
||||
from dbus_next.glib import MessageBus
|
||||
|
||||
has_gi = check_gi_repository()
|
||||
|
||||
if has_gi:
|
||||
from gi.repository import GLib
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_gi, reason=skip_reason_no_gi)
|
||||
def test_standard_interfaces():
|
||||
bus = MessageBus().connect_sync()
|
||||
msg = Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="ListNames",
|
||||
serial=bus.next_serial(),
|
||||
)
|
||||
reply = bus.call_sync(msg)
|
||||
|
||||
assert reply.message_type == MessageType.METHOD_RETURN
|
||||
assert reply.reply_serial == msg.serial
|
||||
assert reply.signature == "as"
|
||||
assert bus.unique_name in reply.body[0]
|
||||
|
||||
msg.interface = "org.freedesktop.DBus.Introspectable"
|
||||
msg.member = "Introspect"
|
||||
msg.serial = bus.next_serial()
|
||||
|
||||
reply = bus.call_sync(msg)
|
||||
assert reply.message_type == MessageType.METHOD_RETURN
|
||||
assert reply.reply_serial == msg.serial
|
||||
assert reply.signature == "s"
|
||||
assert type(reply.body[0]) is str
|
||||
|
||||
msg.member = "MemberDoesNotExist"
|
||||
msg.serial = bus.next_serial()
|
||||
|
||||
reply = bus.call_sync(msg)
|
||||
assert reply.message_type == MessageType.ERROR
|
||||
assert reply.reply_serial == msg.serial
|
||||
assert reply.error_name
|
||||
assert reply.signature == "s"
|
||||
assert type(reply.body[0]) is str
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_gi, reason=skip_reason_no_gi)
|
||||
def test_sending_messages_between_buses():
|
||||
bus1 = MessageBus().connect_sync()
|
||||
bus2 = MessageBus().connect_sync()
|
||||
|
||||
msg = Message(
|
||||
destination=bus1.unique_name,
|
||||
path="/org/test/path",
|
||||
interface="org.test.iface",
|
||||
member="SomeMember",
|
||||
serial=bus2.next_serial(),
|
||||
)
|
||||
|
||||
def message_handler(sent):
|
||||
if sent.sender == bus2.unique_name and sent.serial == msg.serial:
|
||||
assert sent.path == msg.path
|
||||
assert sent.serial == msg.serial
|
||||
assert sent.interface == msg.interface
|
||||
assert sent.member == msg.member
|
||||
bus1.send(Message.new_method_return(sent, "s", ["got it"]))
|
||||
bus1.remove_message_handler(message_handler)
|
||||
return True
|
||||
|
||||
bus1.add_message_handler(message_handler)
|
||||
|
||||
reply = bus2.call_sync(msg)
|
||||
|
||||
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
|
||||
assert reply.sender == bus1.unique_name
|
||||
assert reply.signature == "s"
|
||||
assert reply.body == ["got it"]
|
||||
assert reply.reply_serial == msg.serial
|
||||
|
||||
def message_handler_error(sent):
|
||||
if sent.sender == bus2.unique_name and sent.serial == msg.serial:
|
||||
assert sent.path == msg.path
|
||||
assert sent.serial == msg.serial
|
||||
assert sent.interface == msg.interface
|
||||
assert sent.member == msg.member
|
||||
bus1.send(Message.new_error(sent, "org.test.Error", "throwing an error"))
|
||||
bus1.remove_message_handler(message_handler_error)
|
||||
return True
|
||||
|
||||
bus1.add_message_handler(message_handler_error)
|
||||
|
||||
msg.serial = bus2.next_serial()
|
||||
|
||||
reply = bus2.call_sync(msg)
|
||||
|
||||
assert reply.message_type == MessageType.ERROR
|
||||
assert reply.sender == bus1.unique_name
|
||||
assert reply.reply_serial == msg.serial
|
||||
assert reply.error_name == "org.test.Error"
|
||||
assert reply.signature == "s"
|
||||
assert reply.body == ["throwing an error"]
|
||||
|
||||
msg.serial = bus2.next_serial()
|
||||
msg.flags = MessageFlag.NO_REPLY_EXPECTED
|
||||
reply = bus2.call_sync(msg)
|
||||
assert reply is None
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_gi, reason=skip_reason_no_gi)
|
||||
def test_sending_signals_between_buses():
|
||||
bus1 = MessageBus().connect_sync()
|
||||
bus2 = MessageBus().connect_sync()
|
||||
|
||||
add_match_msg = Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="AddMatch",
|
||||
signature="s",
|
||||
body=[f"sender={bus2.unique_name}"],
|
||||
)
|
||||
|
||||
bus1.call_sync(add_match_msg)
|
||||
|
||||
main = GLib.MainLoop()
|
||||
|
||||
def wait_for_message():
|
||||
ret = None
|
||||
|
||||
def message_handler(signal):
|
||||
nonlocal ret
|
||||
if signal.sender == bus2.unique_name:
|
||||
ret = signal
|
||||
bus1.remove_message_handler(message_handler)
|
||||
main.quit()
|
||||
|
||||
bus1.add_message_handler(message_handler)
|
||||
main.run()
|
||||
return ret
|
||||
|
||||
bus2.send(
|
||||
Message.new_signal(
|
||||
"/org/test/path", "org.test.interface", "SomeSignal", "s", ["a signal"]
|
||||
)
|
||||
)
|
||||
|
||||
signal = wait_for_message()
|
||||
|
||||
assert signal.message_type == MessageType.SIGNAL
|
||||
assert signal.path == "/org/test/path"
|
||||
assert signal.interface == "org.test.interface"
|
||||
assert signal.member == "SomeSignal"
|
||||
assert signal.signature == "s"
|
||||
assert signal.body == ["a signal"]
|
||||
123
tests/test_introspection.py
Normal file
123
tests/test_introspection.py
Normal file
@@ -0,0 +1,123 @@
|
||||
import os
|
||||
|
||||
from dbus_next import ArgDirection, PropertyAccess, SignatureType
|
||||
from dbus_next import introspection as intr
|
||||
|
||||
example_data = open(f"{os.path.dirname(__file__)}/data/introspection.xml").read()
|
||||
|
||||
|
||||
def test_example_introspection_from_xml():
|
||||
node = intr.Node.parse(example_data)
|
||||
|
||||
assert len(node.interfaces) == 1
|
||||
interface = node.interfaces[0]
|
||||
|
||||
assert len(node.nodes) == 2
|
||||
assert len(interface.methods) == 3
|
||||
assert len(interface.signals) == 2
|
||||
assert len(interface.properties) == 1
|
||||
|
||||
assert type(node.nodes[0]) is intr.Node
|
||||
assert node.nodes[0].name == "child_of_sample_object"
|
||||
assert type(node.nodes[1]) is intr.Node
|
||||
assert node.nodes[1].name == "another_child_of_sample_object"
|
||||
|
||||
assert interface.name == "com.example.SampleInterface0"
|
||||
|
||||
frobate = interface.methods[0]
|
||||
assert type(frobate) is intr.Method
|
||||
assert frobate.name == "Frobate"
|
||||
assert len(frobate.in_args) == 1
|
||||
assert len(frobate.out_args) == 2
|
||||
|
||||
foo = frobate.in_args[0]
|
||||
assert type(foo) is intr.Arg
|
||||
assert foo.name == "foo"
|
||||
assert foo.direction == ArgDirection.IN
|
||||
assert foo.signature == "i"
|
||||
assert type(foo.type) is SignatureType
|
||||
assert foo.type.token == "i"
|
||||
|
||||
bar = frobate.out_args[0]
|
||||
assert type(bar) is intr.Arg
|
||||
assert bar.name == "bar"
|
||||
assert bar.direction == ArgDirection.OUT
|
||||
assert bar.signature == "s"
|
||||
assert type(bar.type) is SignatureType
|
||||
assert bar.type.token == "s"
|
||||
|
||||
prop = interface.properties[0]
|
||||
assert type(prop) is intr.Property
|
||||
assert prop.name == "Bar"
|
||||
assert prop.signature == "y"
|
||||
assert type(prop.type) is SignatureType
|
||||
assert prop.type.token == "y"
|
||||
assert prop.access == PropertyAccess.WRITE
|
||||
|
||||
changed = interface.signals[0]
|
||||
assert type(changed) is intr.Signal
|
||||
assert changed.name == "Changed"
|
||||
assert len(changed.args) == 1
|
||||
new_value = changed.args[0]
|
||||
assert type(new_value) is intr.Arg
|
||||
assert new_value.name == "new_value"
|
||||
assert new_value.signature == "b"
|
||||
|
||||
|
||||
def test_example_introspection_to_xml():
|
||||
node = intr.Node.parse(example_data)
|
||||
tree = node.to_xml()
|
||||
assert tree.tag == "node"
|
||||
assert tree.attrib.get("name") == "/com/example/sample_object0"
|
||||
assert len(tree) == 3
|
||||
interface = tree[0]
|
||||
assert interface.tag == "interface"
|
||||
assert interface.get("name") == "com.example.SampleInterface0"
|
||||
assert len(interface) == 6
|
||||
method = interface[0]
|
||||
assert method.tag == "method"
|
||||
assert method.get("name") == "Frobate"
|
||||
# TODO annotations
|
||||
assert len(method) == 3
|
||||
|
||||
arg = method[0]
|
||||
assert arg.tag == "arg"
|
||||
assert arg.attrib.get("name") == "foo"
|
||||
assert arg.attrib.get("type") == "i"
|
||||
assert arg.attrib.get("direction") == "in"
|
||||
|
||||
signal = interface[3]
|
||||
assert signal.tag == "signal"
|
||||
assert signal.attrib.get("name") == "Changed"
|
||||
assert len(signal) == 1
|
||||
|
||||
arg = signal[0]
|
||||
assert arg.tag == "arg"
|
||||
assert arg.attrib.get("name") == "new_value"
|
||||
assert arg.attrib.get("type") == "b"
|
||||
|
||||
signal = interface[4]
|
||||
assert signal.tag == "signal"
|
||||
assert signal.attrib.get("name") == "ChangedMulti"
|
||||
assert len(signal) == 2
|
||||
|
||||
arg = signal[0]
|
||||
assert arg.tag == "arg"
|
||||
assert arg.attrib.get("name") == "new_value1"
|
||||
assert arg.attrib.get("type") == "b"
|
||||
|
||||
arg = signal[1]
|
||||
assert arg.tag == "arg"
|
||||
assert arg.attrib.get("name") == "new_value2"
|
||||
assert arg.attrib.get("type") == "y"
|
||||
|
||||
prop = interface[5]
|
||||
assert prop.attrib.get("name") == "Bar"
|
||||
assert prop.attrib.get("type") == "y"
|
||||
assert prop.attrib.get("access") == "write"
|
||||
|
||||
|
||||
def test_default_interfaces():
|
||||
# just make sure it doesn't throw
|
||||
default = intr.Node.default()
|
||||
assert type(default) is intr.Node
|
||||
122
tests/test_marshaller.py
Normal file
122
tests/test_marshaller.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
|
||||
from dbus_next import Message, SignatureTree, Variant
|
||||
from dbus_next._private.unmarshaller import Unmarshaller
|
||||
|
||||
|
||||
def print_buf(buf):
|
||||
i = 0
|
||||
while True:
|
||||
p = buf[i : i + 8]
|
||||
if not p:
|
||||
break
|
||||
print(p)
|
||||
i += 8
|
||||
|
||||
|
||||
# these messages have been verified with another library
|
||||
table = json.load(open(os.path.dirname(__file__) + "/data/messages.json"))
|
||||
|
||||
|
||||
# variants are an object in the json
|
||||
def replace_variants(type_, item):
|
||||
if type_.token == "v" and type(item) is not Variant:
|
||||
item = Variant(
|
||||
item["signature"],
|
||||
replace_variants(SignatureTree(item["signature"]).types[0], item["value"]),
|
||||
)
|
||||
elif type_.token == "a":
|
||||
for i, item_child in enumerate(item):
|
||||
if type_.children[0].token == "{":
|
||||
for k, v in item.items():
|
||||
item[k] = replace_variants(type_.children[0].children[1], v)
|
||||
else:
|
||||
item[i] = replace_variants(type_.children[0], item_child)
|
||||
elif type_.token == "(":
|
||||
for i, item_child in enumerate(item):
|
||||
if type_.children[0].token == "{":
|
||||
assert False
|
||||
else:
|
||||
item[i] = replace_variants(type_.children[i], item_child)
|
||||
|
||||
return item
|
||||
|
||||
|
||||
def json_dump(what):
|
||||
def dumper(obj):
|
||||
try:
|
||||
return obj.toJSON()
|
||||
except Exception:
|
||||
return obj.__dict__
|
||||
|
||||
return json.dumps(what, default=dumper, indent=2)
|
||||
|
||||
|
||||
def test_marshalling_with_table():
|
||||
for item in table:
|
||||
message = Message(**item["message"])
|
||||
|
||||
body = []
|
||||
for i, type_ in enumerate(message.signature_tree.types):
|
||||
body.append(replace_variants(type_, message.body[i]))
|
||||
message.body = body
|
||||
|
||||
buf = message._marshall()
|
||||
data = bytes.fromhex(item["data"])
|
||||
|
||||
if buf != data:
|
||||
print("message:")
|
||||
print(json_dump(item["message"]))
|
||||
print("")
|
||||
print("mine:")
|
||||
print_buf(bytes(buf))
|
||||
print("")
|
||||
print("theirs:")
|
||||
print_buf(data)
|
||||
|
||||
assert buf == data
|
||||
|
||||
|
||||
def test_unmarshalling_with_table():
|
||||
for item in table:
|
||||
|
||||
stream = io.BytesIO(bytes.fromhex(item["data"]))
|
||||
unmarshaller = Unmarshaller(stream)
|
||||
try:
|
||||
unmarshaller.unmarshall()
|
||||
except Exception as e:
|
||||
print("message failed to unmarshall:")
|
||||
print(json_dump(item["message"]))
|
||||
raise e
|
||||
|
||||
message = Message(**item["message"])
|
||||
|
||||
body = []
|
||||
for i, type_ in enumerate(message.signature_tree.types):
|
||||
body.append(replace_variants(type_, message.body[i]))
|
||||
message.body = body
|
||||
|
||||
for attr in [
|
||||
"body",
|
||||
"signature",
|
||||
"message_type",
|
||||
"destination",
|
||||
"path",
|
||||
"interface",
|
||||
"member",
|
||||
"flags",
|
||||
"serial",
|
||||
]:
|
||||
assert getattr(unmarshaller.message, attr) == getattr(
|
||||
message, attr
|
||||
), f"attr doesnt match: {attr}"
|
||||
|
||||
|
||||
def test_ay_buffer():
|
||||
body = [bytes(10000)]
|
||||
msg = Message(path="/test", member="test", signature="ay", body=body)
|
||||
marshalled = msg._marshall()
|
||||
unmarshalled_msg = Unmarshaller(io.BytesIO(marshalled)).unmarshall()
|
||||
assert unmarshalled_msg.body[0] == body[0]
|
||||
82
tests/test_request_name.py
Normal file
82
tests/test_request_name.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from test.util import check_gi_repository, skip_reason_no_gi
|
||||
|
||||
import pytest
|
||||
from dbus_next import (
|
||||
Message,
|
||||
MessageType,
|
||||
NameFlag,
|
||||
ReleaseNameReply,
|
||||
RequestNameReply,
|
||||
aio,
|
||||
glib,
|
||||
)
|
||||
|
||||
has_gi = check_gi_repository()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_name_requests():
|
||||
test_name = "aio.test.request.name"
|
||||
|
||||
bus1 = await aio.MessageBus().connect()
|
||||
bus2 = await aio.MessageBus().connect()
|
||||
|
||||
async def get_name_owner(name):
|
||||
reply = await bus1.call(
|
||||
Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus",
|
||||
member="GetNameOwner",
|
||||
signature="s",
|
||||
body=[name],
|
||||
)
|
||||
)
|
||||
|
||||
assert reply.message_type == MessageType.METHOD_RETURN
|
||||
return reply.body[0]
|
||||
|
||||
reply = await bus1.request_name(test_name)
|
||||
assert reply == RequestNameReply.PRIMARY_OWNER
|
||||
reply = await bus1.request_name(test_name)
|
||||
assert reply == RequestNameReply.ALREADY_OWNER
|
||||
|
||||
reply = await bus2.request_name(test_name, NameFlag.ALLOW_REPLACEMENT)
|
||||
assert reply == RequestNameReply.IN_QUEUE
|
||||
|
||||
reply = await bus1.release_name(test_name)
|
||||
assert reply == ReleaseNameReply.RELEASED
|
||||
|
||||
reply = await bus1.release_name("name.doesnt.exist")
|
||||
assert reply == ReleaseNameReply.NON_EXISTENT
|
||||
|
||||
reply = await bus1.release_name(test_name)
|
||||
assert reply == ReleaseNameReply.NOT_OWNER
|
||||
|
||||
new_owner = await get_name_owner(test_name)
|
||||
assert new_owner == bus2.unique_name
|
||||
|
||||
reply = await bus1.request_name(test_name, NameFlag.DO_NOT_QUEUE)
|
||||
assert reply == RequestNameReply.EXISTS
|
||||
|
||||
reply = await bus1.request_name(
|
||||
test_name, NameFlag.DO_NOT_QUEUE | NameFlag.REPLACE_EXISTING
|
||||
)
|
||||
assert reply == RequestNameReply.PRIMARY_OWNER
|
||||
|
||||
bus1.disconnect()
|
||||
bus2.disconnect()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not has_gi, reason=skip_reason_no_gi)
|
||||
def test_request_name_glib():
|
||||
test_name = "glib.test.request.name"
|
||||
bus = glib.MessageBus().connect_sync()
|
||||
|
||||
reply = bus.request_name_sync(test_name)
|
||||
assert reply == RequestNameReply.PRIMARY_OWNER
|
||||
|
||||
reply = bus.release_name_sync(test_name)
|
||||
assert reply == ReleaseNameReply.RELEASED
|
||||
|
||||
bus.disconnect()
|
||||
221
tests/test_signature.py
Normal file
221
tests/test_signature.py
Normal file
@@ -0,0 +1,221 @@
|
||||
import pytest
|
||||
from dbus_next import SignatureBodyMismatchError, SignatureTree, Variant
|
||||
from dbus_next._private.util import signature_contains_type
|
||||
|
||||
|
||||
def assert_simple_type(signature, type_):
|
||||
assert type_.token == signature
|
||||
assert type_.signature == signature
|
||||
assert len(type_.children) == 0
|
||||
|
||||
|
||||
def test_simple():
|
||||
tree = SignatureTree("s")
|
||||
assert len(tree.types) == 1
|
||||
assert_simple_type("s", tree.types[0])
|
||||
|
||||
|
||||
def test_multiple_simple():
|
||||
tree = SignatureTree("sss")
|
||||
assert len(tree.types) == 3
|
||||
for i in range(0, 3):
|
||||
assert_simple_type("s", tree.types[i])
|
||||
|
||||
|
||||
def test_array():
|
||||
tree = SignatureTree("as")
|
||||
assert len(tree.types) == 1
|
||||
child = tree.types[0]
|
||||
assert child.signature == "as"
|
||||
assert child.token == "a"
|
||||
assert len(child.children) == 1
|
||||
assert_simple_type("s", child.children[0])
|
||||
|
||||
|
||||
def test_array_multiple():
|
||||
tree = SignatureTree("asasass")
|
||||
assert len(tree.types) == 4
|
||||
assert_simple_type("s", tree.types[3])
|
||||
for i in range(0, 3):
|
||||
array_child = tree.types[i]
|
||||
assert array_child.token == "a"
|
||||
assert array_child.signature == "as"
|
||||
assert len(array_child.children) == 1
|
||||
assert_simple_type("s", array_child.children[0])
|
||||
|
||||
|
||||
def test_array_nested():
|
||||
tree = SignatureTree("aas")
|
||||
assert len(tree.types) == 1
|
||||
child = tree.types[0]
|
||||
assert child.token == "a"
|
||||
assert child.signature == "aas"
|
||||
assert len(child.children) == 1
|
||||
nested_child = child.children[0]
|
||||
assert nested_child.token == "a"
|
||||
assert nested_child.signature == "as"
|
||||
assert len(nested_child.children) == 1
|
||||
assert_simple_type("s", nested_child.children[0])
|
||||
|
||||
|
||||
def test_simple_struct():
|
||||
tree = SignatureTree("(sss)")
|
||||
assert len(tree.types) == 1
|
||||
child = tree.types[0]
|
||||
assert child.signature == "(sss)"
|
||||
assert len(child.children) == 3
|
||||
for i in range(0, 3):
|
||||
assert_simple_type("s", child.children[i])
|
||||
|
||||
|
||||
def test_nested_struct():
|
||||
tree = SignatureTree("(s(s(s)))")
|
||||
assert len(tree.types) == 1
|
||||
child = tree.types[0]
|
||||
assert child.signature == "(s(s(s)))"
|
||||
assert child.token == "("
|
||||
assert len(child.children) == 2
|
||||
assert_simple_type("s", child.children[0])
|
||||
first_nested = child.children[1]
|
||||
assert first_nested.token == "("
|
||||
assert first_nested.signature == "(s(s))"
|
||||
assert len(first_nested.children) == 2
|
||||
assert_simple_type("s", first_nested.children[0])
|
||||
second_nested = first_nested.children[1]
|
||||
assert second_nested.token == "("
|
||||
assert second_nested.signature == "(s)"
|
||||
assert len(second_nested.children) == 1
|
||||
assert_simple_type("s", second_nested.children[0])
|
||||
|
||||
|
||||
def test_struct_multiple():
|
||||
tree = SignatureTree("(s)(s)(s)")
|
||||
assert len(tree.types) == 3
|
||||
for i in range(0, 3):
|
||||
child = tree.types[0]
|
||||
assert child.token == "("
|
||||
assert child.signature == "(s)"
|
||||
assert len(child.children) == 1
|
||||
assert_simple_type("s", child.children[0])
|
||||
|
||||
|
||||
def test_array_of_structs():
|
||||
tree = SignatureTree("a(ss)")
|
||||
assert len(tree.types) == 1
|
||||
child = tree.types[0]
|
||||
assert child.token == "a"
|
||||
assert child.signature == "a(ss)"
|
||||
assert len(child.children) == 1
|
||||
struct_child = child.children[0]
|
||||
assert struct_child.token == "("
|
||||
assert struct_child.signature == "(ss)"
|
||||
assert len(struct_child.children) == 2
|
||||
for i in range(0, 2):
|
||||
assert_simple_type("s", struct_child.children[i])
|
||||
|
||||
|
||||
def test_dict_simple():
|
||||
tree = SignatureTree("a{ss}")
|
||||
assert len(tree.types) == 1
|
||||
child = tree.types[0]
|
||||
assert child.signature == "a{ss}"
|
||||
assert child.token == "a"
|
||||
assert len(child.children) == 1
|
||||
dict_child = child.children[0]
|
||||
assert dict_child.token == "{"
|
||||
assert dict_child.signature == "{ss}"
|
||||
assert len(dict_child.children) == 2
|
||||
assert_simple_type("s", dict_child.children[0])
|
||||
assert_simple_type("s", dict_child.children[1])
|
||||
|
||||
|
||||
def test_dict_of_structs():
|
||||
tree = SignatureTree("a{s(ss)}")
|
||||
assert len(tree.types) == 1
|
||||
child = tree.types[0]
|
||||
assert child.token == "a"
|
||||
assert child.signature == "a{s(ss)}"
|
||||
assert len(child.children) == 1
|
||||
dict_child = child.children[0]
|
||||
assert dict_child.token == "{"
|
||||
assert dict_child.signature == "{s(ss)}"
|
||||
assert len(dict_child.children) == 2
|
||||
assert_simple_type("s", dict_child.children[0])
|
||||
struct_child = dict_child.children[1]
|
||||
assert struct_child.token == "("
|
||||
assert struct_child.signature == "(ss)"
|
||||
assert len(struct_child.children) == 2
|
||||
for i in range(0, 2):
|
||||
assert_simple_type("s", struct_child.children[i])
|
||||
|
||||
|
||||
def test_contains_type():
|
||||
tree = SignatureTree("h")
|
||||
assert signature_contains_type(tree, [0], "h")
|
||||
assert not signature_contains_type(tree, [0], "u")
|
||||
|
||||
tree = SignatureTree("ah")
|
||||
assert signature_contains_type(tree, [[0]], "h")
|
||||
assert signature_contains_type(tree, [[0]], "a")
|
||||
assert not signature_contains_type(tree, [[0]], "u")
|
||||
|
||||
tree = SignatureTree("av")
|
||||
body = [
|
||||
[
|
||||
Variant("u", 0),
|
||||
Variant("i", 0),
|
||||
Variant("x", 0),
|
||||
Variant("v", Variant("s", "hi")),
|
||||
]
|
||||
]
|
||||
assert signature_contains_type(tree, body, "u")
|
||||
assert signature_contains_type(tree, body, "x")
|
||||
assert signature_contains_type(tree, body, "v")
|
||||
assert signature_contains_type(tree, body, "s")
|
||||
assert not signature_contains_type(tree, body, "o")
|
||||
|
||||
tree = SignatureTree("a{sv}")
|
||||
body = {
|
||||
"foo": Variant("h", 0),
|
||||
"bar": Variant("i", 0),
|
||||
"bat": Variant("x", 0),
|
||||
"baz": Variant("v", Variant("o", "/hi")),
|
||||
}
|
||||
for expected in "hixvso":
|
||||
assert signature_contains_type(tree, [body], expected)
|
||||
assert not signature_contains_type(tree, [body], "b")
|
||||
|
||||
|
||||
def test_invalid_variants():
|
||||
tree = SignatureTree("a{sa{sv}}")
|
||||
s_con = {
|
||||
"type": "802-11-wireless",
|
||||
"uuid": "1234",
|
||||
"id": "SSID",
|
||||
}
|
||||
|
||||
s_wifi = {
|
||||
"ssid": "SSID",
|
||||
"mode": "infrastructure",
|
||||
"hidden": True,
|
||||
}
|
||||
|
||||
s_wsec = {
|
||||
"key-mgmt": "wpa-psk",
|
||||
"auth-alg": "open",
|
||||
"psk": "PASSWORD",
|
||||
}
|
||||
|
||||
s_ip4 = {"method": "auto"}
|
||||
s_ip6 = {"method": "auto"}
|
||||
|
||||
con = {
|
||||
"connection": s_con,
|
||||
"802-11-wireless": s_wifi,
|
||||
"802-11-wireless-security": s_wsec,
|
||||
"ipv4": s_ip4,
|
||||
"ipv6": s_ip6,
|
||||
}
|
||||
|
||||
with pytest.raises(SignatureBodyMismatchError):
|
||||
tree.verify([con])
|
||||
70
tests/test_tcp_address.py
Normal file
70
tests/test_tcp_address.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
import pytest
|
||||
from dbus_next import Message
|
||||
from dbus_next._private.address import parse_address
|
||||
from dbus_next.aio import MessageBus
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tcp_connection_with_forwarding(event_loop):
|
||||
closables = []
|
||||
host = "127.0.0.1"
|
||||
port = "55556"
|
||||
|
||||
addr_info = parse_address(os.environ.get("DBUS_SESSION_BUS_ADDRESS"))
|
||||
assert addr_info
|
||||
assert "abstract" in addr_info[0][1]
|
||||
path = f'\0{addr_info[0][1]["abstract"]}'
|
||||
|
||||
async def handle_connection(tcp_reader, tcp_writer):
|
||||
unix_reader, unix_writer = await asyncio.open_unix_connection(path)
|
||||
closables.append(tcp_writer)
|
||||
closables.append(unix_writer)
|
||||
|
||||
async def handle_read():
|
||||
while True:
|
||||
data = await tcp_reader.read(1)
|
||||
if not data:
|
||||
break
|
||||
unix_writer.write(data)
|
||||
|
||||
async def handle_write():
|
||||
while True:
|
||||
data = await unix_reader.read(1)
|
||||
if not data:
|
||||
break
|
||||
tcp_writer.write(data)
|
||||
|
||||
asyncio.run_coroutine_threadsafe(handle_read(), event_loop)
|
||||
asyncio.run_coroutine_threadsafe(handle_write(), event_loop)
|
||||
|
||||
server = await asyncio.start_server(handle_connection, host, port)
|
||||
closables.append(server)
|
||||
|
||||
bus = await MessageBus(bus_address=f"tcp:host={host},port={port}").connect()
|
||||
|
||||
# basic tests to see if it works
|
||||
result = await bus.call(
|
||||
Message(
|
||||
destination="org.freedesktop.DBus",
|
||||
path="/org/freedesktop/DBus",
|
||||
interface="org.freedesktop.DBus.Peer",
|
||||
member="Ping",
|
||||
)
|
||||
)
|
||||
assert result
|
||||
|
||||
intr = await bus.introspect("org.freedesktop.DBus", "/org/freedesktop/DBus")
|
||||
obj = bus.get_proxy_object("org.freedesktop.DBus", "/org/freedesktop/DBus", intr)
|
||||
iface = obj.get_interface("org.freedesktop.DBus.Peer")
|
||||
await iface.call_ping()
|
||||
|
||||
assert bus._sock.getpeername()[0] == host
|
||||
assert bus._sock.getsockname()[0] == host
|
||||
assert bus._sock.gettimeout() == 0
|
||||
assert bus._stream.closed is False
|
||||
|
||||
for c in closables:
|
||||
c.close()
|
||||
90
tests/test_validators.py
Normal file
90
tests/test_validators.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from dbus_next import (
|
||||
is_bus_name_valid,
|
||||
is_interface_name_valid,
|
||||
is_member_name_valid,
|
||||
is_object_path_valid,
|
||||
)
|
||||
|
||||
|
||||
def test_object_path_validator():
|
||||
valid_paths = ["/", "/foo", "/foo/bar", "/foo/bar/bat"]
|
||||
invalid_paths = [
|
||||
None,
|
||||
{},
|
||||
"",
|
||||
"foo",
|
||||
"foo/bar",
|
||||
"/foo/bar/",
|
||||
"/$/foo/bar",
|
||||
"/foo//bar",
|
||||
"/foo$bar/baz",
|
||||
]
|
||||
|
||||
for path in valid_paths:
|
||||
assert is_object_path_valid(path), f'path should be valid: "{path}"'
|
||||
for path in invalid_paths:
|
||||
assert not is_object_path_valid(path), f'path should be invalid: "{path}"'
|
||||
|
||||
|
||||
def test_bus_name_validator():
|
||||
valid_names = [
|
||||
"foo.bar",
|
||||
"foo.bar.bat",
|
||||
"_foo._bar",
|
||||
"foo.bar69",
|
||||
"foo.bar-69",
|
||||
"org.mpris.MediaPlayer2.google-play-desktop-player",
|
||||
]
|
||||
invalid_names = [
|
||||
None,
|
||||
{},
|
||||
"",
|
||||
"5foo.bar",
|
||||
"foo.6bar",
|
||||
".foo.bar",
|
||||
"bar..baz",
|
||||
"$foo.bar",
|
||||
"foo$.ba$r",
|
||||
]
|
||||
|
||||
for name in valid_names:
|
||||
assert is_bus_name_valid(name), f'bus name should be valid: "{name}"'
|
||||
for name in invalid_names:
|
||||
assert not is_bus_name_valid(name), f'bus name should be invalid: "{name}"'
|
||||
|
||||
|
||||
def test_interface_name_validator():
|
||||
valid_names = ["foo.bar", "foo.bar.bat", "_foo._bar", "foo.bar69"]
|
||||
invalid_names = [
|
||||
None,
|
||||
{},
|
||||
"",
|
||||
"5foo.bar",
|
||||
"foo.6bar",
|
||||
".foo.bar",
|
||||
"bar..baz",
|
||||
"$foo.bar",
|
||||
"foo$.ba$r",
|
||||
"org.mpris.MediaPlayer2.google-play-desktop-player",
|
||||
]
|
||||
|
||||
for name in valid_names:
|
||||
assert is_interface_name_valid(
|
||||
name
|
||||
), f'interface name should be valid: "{name}"'
|
||||
for name in invalid_names:
|
||||
assert not is_interface_name_valid(
|
||||
name
|
||||
), f'interface name should be invalid: "{name}"'
|
||||
|
||||
|
||||
def test_member_name_validator():
|
||||
valid_members = ["foo", "FooBar", "Bat_Baz69", "foo-bar"]
|
||||
invalid_members = [None, {}, "", "foo.bar", "5foo", "foo$bar"]
|
||||
|
||||
for member in valid_members:
|
||||
assert is_member_name_valid(member), f'member name should be valid: "{member}"'
|
||||
for member in invalid_members:
|
||||
assert not is_member_name_valid(
|
||||
member
|
||||
), f'member name should be invalid: "{member}"'
|
||||
16
tests/util.py
Normal file
16
tests/util.py
Normal file
@@ -0,0 +1,16 @@
|
||||
_has_gi = None
|
||||
skip_reason_no_gi = "glib tests require python3-gi"
|
||||
|
||||
|
||||
def check_gi_repository():
|
||||
global _has_gi
|
||||
if _has_gi is not None:
|
||||
return _has_gi
|
||||
try:
|
||||
from gi.repository import GLib
|
||||
|
||||
_has_gi = True
|
||||
return _has_gi
|
||||
except ImportError:
|
||||
_has_gi = False
|
||||
return _has_gi
|
||||
Reference in New Issue
Block a user