chore: initial port

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

View File

@@ -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()

View 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()

View 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()

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

View File

View File

@@ -0,0 +1,151 @@
from dbus_next import PropertyAccess
from dbus_next import introspection as intr
from dbus_next.service import ServiceInterface, dbus_property, method, signal
class ExampleInterface(ServiceInterface):
def __init__(self):
super().__init__("test.interface")
self._some_prop = 55
self._another_prop = 101
self._weird_prop = 500
@method()
def some_method(self, one: "s", two: "s") -> "s":
return "hello"
@method(name="renamed_method", disabled=True)
def another_method(self, eight: "o", six: "t"):
pass
@signal()
def some_signal(self) -> "as":
return ["result"]
@signal(name="renamed_signal", disabled=True)
def another_signal(self) -> "(dodo)":
return [1, "/", 1, "/"]
@dbus_property(
name="renamed_readonly_property", access=PropertyAccess.READ, disabled=True
)
def another_prop(self) -> "t":
return self._another_prop
@dbus_property()
def some_prop(self) -> "u":
return self._some_prop
@some_prop.setter
def some_prop(self, val: "u"):
self._some_prop = val + 1
# for this one, the setter has a different name than the getter which is a
# special case in the code
@dbus_property()
def weird_prop(self) -> "t":
return self._weird_prop
@weird_prop.setter
def setter_for_weird_prop(self, val: "t"):
self._weird_prop = val
def test_method_decorator():
interface = ExampleInterface()
assert interface.name == "test.interface"
properties = ServiceInterface._get_properties(interface)
methods = ServiceInterface._get_methods(interface)
signals = ServiceInterface._get_signals(interface)
assert len(methods) == 2
method = methods[0]
assert method.name == "renamed_method"
assert method.in_signature == "ot"
assert method.out_signature == ""
assert method.disabled
assert type(method.introspection) is intr.Method
method = methods[1]
assert method.name == "some_method"
assert method.in_signature == "ss"
assert method.out_signature == "s"
assert not method.disabled
assert type(method.introspection) is intr.Method
assert len(signals) == 2
signal = signals[0]
assert signal.name == "renamed_signal"
assert signal.signature == "(dodo)"
assert signal.disabled
assert type(signal.introspection) is intr.Signal
signal = signals[1]
assert signal.name == "some_signal"
assert signal.signature == "as"
assert not signal.disabled
assert type(signal.introspection) is intr.Signal
assert len(properties) == 3
renamed_readonly_prop = properties[0]
assert renamed_readonly_prop.name == "renamed_readonly_property"
assert renamed_readonly_prop.signature == "t"
assert renamed_readonly_prop.access == PropertyAccess.READ
assert renamed_readonly_prop.disabled
assert type(renamed_readonly_prop.introspection) is intr.Property
weird_prop = properties[1]
assert weird_prop.name == "weird_prop"
assert weird_prop.access == PropertyAccess.READWRITE
assert weird_prop.signature == "t"
assert not weird_prop.disabled
assert weird_prop.prop_getter is not None
assert weird_prop.prop_getter.__name__ == "weird_prop"
assert weird_prop.prop_setter is not None
assert weird_prop.prop_setter.__name__ == "setter_for_weird_prop"
assert type(weird_prop.introspection) is intr.Property
prop = properties[2]
assert prop.name == "some_prop"
assert prop.access == PropertyAccess.READWRITE
assert prop.signature == "u"
assert not prop.disabled
assert prop.prop_getter is not None
assert prop.prop_setter is not None
assert type(prop.introspection) is intr.Property
# make sure the getter and setter actually work
assert interface._some_prop == 55
interface._some_prop = 555
assert interface.some_prop == 555
assert interface._weird_prop == 500
assert weird_prop.prop_getter(interface) == 500
interface._weird_prop = 1001
assert interface._weird_prop == 1001
weird_prop.prop_setter(interface, 600)
assert interface._weird_prop == 600
def test_interface_introspection():
interface = ExampleInterface()
intr_interface = interface.introspect()
assert type(intr_interface) is intr.Interface
xml = intr_interface.to_xml()
assert xml.tag == "interface"
assert xml.attrib.get("name", None) == "test.interface"
methods = xml.findall("method")
signals = xml.findall("signal")
properties = xml.findall("property")
assert len(xml) == 4
assert len(methods) == 1
assert len(signals) == 1
assert len(properties) == 2

View File

@@ -0,0 +1,118 @@
import pytest
from dbus_next import Message, MessageType
from dbus_next import introspection as intr
from dbus_next.aio import MessageBus
from dbus_next.service import ServiceInterface, method
standard_interfaces_count = len(intr.Node.default().interfaces)
class ExampleInterface(ServiceInterface):
def __init__(self, name):
self._method_called = False
super().__init__(name)
@method()
def some_method(self):
self._method_called = True
@pytest.mark.asyncio
async def test_export_unexport():
interface = ExampleInterface("test.interface")
interface2 = ExampleInterface("test.interface2")
export_path = "/test/path"
export_path2 = "/test/path/child"
bus = await MessageBus().connect()
bus.export(export_path, interface)
assert export_path in bus._path_exports
assert len(bus._path_exports[export_path]) == 1
assert bus._path_exports[export_path][0] is interface
assert len(ServiceInterface._get_buses(interface)) == 1
bus.export(export_path2, interface2)
node = bus._introspect_export_path(export_path)
assert len(node.interfaces) == standard_interfaces_count + 1
assert len(node.nodes) == 1
# relative path
assert node.nodes[0].name == "child"
bus.unexport(export_path, interface)
assert export_path not in bus._path_exports
assert len(ServiceInterface._get_buses(interface)) == 0
bus.export(export_path2, interface)
assert len(bus._path_exports[export_path2]) == 2
# test unexporting the whole path
bus.unexport(export_path2)
assert not bus._path_exports
assert not ServiceInterface._get_buses(interface)
assert not ServiceInterface._get_buses(interface2)
# test unexporting by name
bus.export(export_path, interface)
bus.unexport(export_path, interface.name)
assert not bus._path_exports
assert not ServiceInterface._get_buses(interface)
node = bus._introspect_export_path("/path/doesnt/exist")
assert type(node) is intr.Node
assert not node.interfaces
assert not node.nodes
@pytest.mark.asyncio
async def test_export_alias():
bus = await MessageBus().connect()
interface = ExampleInterface("test.interface")
export_path = "/test/path"
export_path2 = "/test/path/child"
bus.export(export_path, interface)
bus.export(export_path2, interface)
result = await bus.call(
Message(
destination=bus.unique_name,
path=export_path,
interface="test.interface",
member="some_method",
)
)
assert result.message_type is MessageType.METHOD_RETURN, result.body[0]
assert interface._method_called
interface._method_called = False
result = await bus.call(
Message(
destination=bus.unique_name,
path=export_path2,
interface="test.interface",
member="some_method",
)
)
assert result.message_type is MessageType.METHOD_RETURN, result.body[0]
assert interface._method_called
@pytest.mark.asyncio
async def test_export_introspection():
interface = ExampleInterface("test.interface")
interface2 = ExampleInterface("test.interface2")
export_path = "/test/path"
export_path2 = "/test/path/child"
bus = await MessageBus().connect()
bus.export(export_path, interface)
bus.export(export_path2, interface2)
root = bus._introspect_export_path("/")
assert len(root.nodes) == 1

View File

@@ -0,0 +1,180 @@
import pytest
from dbus_next import (
DBusError,
ErrorType,
Message,
MessageFlag,
MessageType,
SignatureTree,
Variant,
)
from dbus_next.aio import MessageBus
from dbus_next.service import ServiceInterface, method
class ExampleInterface(ServiceInterface):
def __init__(self, name):
super().__init__(name)
@method()
def echo(self, what: "s") -> "s":
assert type(self) is ExampleInterface
return what
@method()
def echo_multiple(self, what1: "s", what2: "s") -> "ss":
assert type(self) is ExampleInterface
return [what1, what2]
@method()
def echo_containers(
self, array: "as", variant: "v", dict_entries: "a{sv}", struct: "(s(s(v)))"
) -> "asva{sv}(s(s(v)))":
assert type(self) is ExampleInterface
return [array, variant, dict_entries, struct]
@method()
def ping(self):
assert type(self) is ExampleInterface
pass
@method(name="renamed")
def original_name(self):
assert type(self) is ExampleInterface
pass
@method(disabled=True)
def not_here(self):
assert type(self) is ExampleInterface
pass
@method()
def throws_unexpected_error(self):
assert type(self) is ExampleInterface
raise Exception("oops")
@method()
def throws_dbus_error(self):
assert type(self) is ExampleInterface
raise DBusError("test.error", "an error ocurred")
class AsyncInterface(ServiceInterface):
def __init__(self, name):
super().__init__(name)
@method()
async def echo(self, what: "s") -> "s":
assert type(self) is AsyncInterface
return what
@method()
async def echo_multiple(self, what1: "s", what2: "s") -> "ss":
assert type(self) is AsyncInterface
return [what1, what2]
@method()
async def echo_containers(
self, array: "as", variant: "v", dict_entries: "a{sv}", struct: "(s(s(v)))"
) -> "asva{sv}(s(s(v)))":
assert type(self) is AsyncInterface
return [array, variant, dict_entries, struct]
@method()
async def ping(self):
assert type(self) is AsyncInterface
pass
@method(name="renamed")
async def original_name(self):
assert type(self) is AsyncInterface
pass
@method(disabled=True)
async def not_here(self):
assert type(self) is AsyncInterface
pass
@method()
async def throws_unexpected_error(self):
assert type(self) is AsyncInterface
raise Exception("oops")
@method()
def throws_dbus_error(self):
assert type(self) is AsyncInterface
raise DBusError("test.error", "an error ocurred")
@pytest.mark.parametrize("interface_class", [ExampleInterface, AsyncInterface])
@pytest.mark.asyncio
async def test_methods(interface_class):
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()
interface = interface_class("test.interface")
export_path = "/test/path"
async def call(member, signature="", body=[], flags=MessageFlag.NONE):
return await bus2.call(
Message(
destination=bus1.unique_name,
path=export_path,
interface=interface.name,
member=member,
signature=signature,
body=body,
flags=flags,
)
)
bus1.export(export_path, interface)
body = ["hello world"]
reply = await call("echo", "s", body)
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
assert reply.signature == "s"
assert reply.body == body
body = ["hello", "world"]
reply = await call("echo_multiple", "ss", body)
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
assert reply.signature == "ss"
assert reply.body == body
body = [
["hello", "world"],
Variant("v", Variant("(ss)", ["hello", "world"])),
{"foo": Variant("t", 100)},
["one", ["two", [Variant("s", "three")]]],
]
signature = "asva{sv}(s(s(v)))"
SignatureTree(signature).verify(body)
reply = await call("echo_containers", signature, body)
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
assert reply.signature == signature
assert reply.body == body
reply = await call("ping")
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
assert reply.signature == ""
assert reply.body == []
reply = await call("throws_unexpected_error")
assert reply.message_type == MessageType.ERROR, reply.body[0]
assert reply.error_name == ErrorType.SERVICE_ERROR.value, reply.body[0]
reply = await call("throws_dbus_error")
assert reply.message_type == MessageType.ERROR, reply.body[0]
assert reply.error_name == "test.error", reply.body[0]
assert reply.body == ["an error ocurred"]
reply = await call("ping", flags=MessageFlag.NO_REPLY_EXPECTED)
assert reply is None
reply = await call("throws_unexpected_error", flags=MessageFlag.NO_REPLY_EXPECTED)
assert reply is None
reply = await call("throws_dbus_error", flags=MessageFlag.NO_REPLY_EXPECTED)
assert reply is None

View File

@@ -0,0 +1,296 @@
import asyncio
import pytest
from dbus_next import (
DBusError,
ErrorType,
Message,
MessageType,
PropertyAccess,
Variant,
)
from dbus_next.aio import MessageBus
from dbus_next.service import ServiceInterface, dbus_property, method
class ExampleInterface(ServiceInterface):
def __init__(self, name):
super().__init__(name)
self._string_prop = "hi"
self._readonly_prop = 100
self._disabled_prop = "1234"
self._container_prop = [["hello", "world"]]
self._renamed_prop = "65"
@dbus_property()
def string_prop(self) -> "s":
return self._string_prop
@string_prop.setter
def string_prop_setter(self, val: "s"):
self._string_prop = val
@dbus_property(PropertyAccess.READ)
def readonly_prop(self) -> "t":
return self._readonly_prop
@dbus_property()
def container_prop(self) -> "a(ss)":
return self._container_prop
@container_prop.setter
def container_prop(self, val: "a(ss)"):
self._container_prop = val
@dbus_property(name="renamed_prop")
def original_name(self) -> "s":
return self._renamed_prop
@original_name.setter
def original_name_setter(self, val: "s"):
self._renamed_prop = val
@dbus_property(disabled=True)
def disabled_prop(self) -> "s":
return self._disabled_prop
@disabled_prop.setter
def disabled_prop(self, val: "s"):
self._disabled_prop = val
@dbus_property(disabled=True)
def throws_error(self) -> "s":
raise DBusError("test.error", "told you so")
@throws_error.setter
def throws_error(self, val: "s"):
raise DBusError("test.error", "told you so")
@dbus_property(PropertyAccess.READ, disabled=True)
def returns_wrong_type(self) -> "s":
return 5
@method()
def do_emit_properties_changed(self):
changed = {"string_prop": "asdf"}
invalidated = ["container_prop"]
self.emit_properties_changed(changed, invalidated)
class AsyncInterface(ServiceInterface):
def __init__(self, name):
super().__init__(name)
self._string_prop = "hi"
self._readonly_prop = 100
self._disabled_prop = "1234"
self._container_prop = [["hello", "world"]]
self._renamed_prop = "65"
@dbus_property()
async def string_prop(self) -> "s":
return self._string_prop
@string_prop.setter
async def string_prop_setter(self, val: "s"):
self._string_prop = val
@dbus_property(PropertyAccess.READ)
async def readonly_prop(self) -> "t":
return self._readonly_prop
@dbus_property()
async def container_prop(self) -> "a(ss)":
return self._container_prop
@container_prop.setter
async def container_prop(self, val: "a(ss)"):
self._container_prop = val
@dbus_property(name="renamed_prop")
async def original_name(self) -> "s":
return self._renamed_prop
@original_name.setter
async def original_name_setter(self, val: "s"):
self._renamed_prop = val
@dbus_property(disabled=True)
async def disabled_prop(self) -> "s":
return self._disabled_prop
@disabled_prop.setter
async def disabled_prop(self, val: "s"):
self._disabled_prop = val
@dbus_property(disabled=True)
async def throws_error(self) -> "s":
raise DBusError("test.error", "told you so")
@throws_error.setter
async def throws_error(self, val: "s"):
raise DBusError("test.error", "told you so")
@dbus_property(PropertyAccess.READ, disabled=True)
async def returns_wrong_type(self) -> "s":
return 5
@method()
def do_emit_properties_changed(self):
changed = {"string_prop": "asdf"}
invalidated = ["container_prop"]
self.emit_properties_changed(changed, invalidated)
@pytest.mark.parametrize("interface_class", [ExampleInterface, AsyncInterface])
@pytest.mark.asyncio
async def test_property_methods(interface_class):
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()
interface = interface_class("test.interface")
export_path = "/test/path"
bus1.export(export_path, interface)
async def call_properties(member, signature, body):
return await bus2.call(
Message(
destination=bus1.unique_name,
path=export_path,
interface="org.freedesktop.DBus.Properties",
member=member,
signature=signature,
body=body,
)
)
result = await call_properties("GetAll", "s", [interface.name])
assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
assert result.signature == "a{sv}"
assert result.body == [
{
"string_prop": Variant("s", interface._string_prop),
"readonly_prop": Variant("t", interface._readonly_prop),
"container_prop": Variant("a(ss)", interface._container_prop),
"renamed_prop": Variant("s", interface._renamed_prop),
}
]
result = await call_properties("Get", "ss", [interface.name, "string_prop"])
assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
assert result.signature == "v"
assert result.body == [Variant("s", "hi")]
result = await call_properties(
"Set", "ssv", [interface.name, "string_prop", Variant("s", "ho")]
)
assert result.message_type == MessageType.METHOD_RETURN, result.body[0]
assert interface._string_prop == "ho"
if interface_class is AsyncInterface:
assert "ho", await interface.string_prop()
else:
assert "ho", interface.string_prop
result = await call_properties(
"Set", "ssv", [interface.name, "readonly_prop", Variant("t", 100)]
)
assert result.message_type == MessageType.ERROR, result.body[0]
assert result.error_name == ErrorType.PROPERTY_READ_ONLY.value, result.body[0]
result = await call_properties(
"Set", "ssv", [interface.name, "disabled_prop", Variant("s", "asdf")]
)
assert result.message_type == MessageType.ERROR, result.body[0]
assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value
result = await call_properties(
"Set", "ssv", [interface.name, "not_a_prop", Variant("s", "asdf")]
)
assert result.message_type == MessageType.ERROR, result.body[0]
assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value
# wrong type
result = await call_properties(
"Set", "ssv", [interface.name, "string_prop", Variant("t", 100)]
)
assert result.message_type == MessageType.ERROR
assert result.error_name == ErrorType.INVALID_SIGNATURE.value
# enable the erroring properties so we can test them
for prop in ServiceInterface._get_properties(interface):
if prop.name in ["throws_error", "returns_wrong_type"]:
prop.disabled = False
result = await call_properties("Get", "ss", [interface.name, "returns_wrong_type"])
assert result.message_type == MessageType.ERROR, result.body[0]
assert result.error_name == ErrorType.SERVICE_ERROR.value
result = await call_properties(
"Set", "ssv", [interface.name, "throws_error", Variant("s", "ho")]
)
assert result.message_type == MessageType.ERROR, result.body[0]
assert result.error_name == "test.error"
assert result.body == ["told you so"]
result = await call_properties("Get", "ss", [interface.name, "throws_error"])
assert result.message_type == MessageType.ERROR, result.body[0]
assert result.error_name == "test.error"
assert result.body == ["told you so"]
result = await call_properties("GetAll", "s", [interface.name])
assert result.message_type == MessageType.ERROR, result.body[0]
assert result.error_name == "test.error"
assert result.body == ["told you so"]
@pytest.mark.parametrize("interface_class", [ExampleInterface, AsyncInterface])
@pytest.mark.asyncio
async def test_property_changed_signal(interface_class):
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()
await bus2.call(
Message(
destination="org.freedesktop.DBus",
path="/org/freedesktop/DBus",
interface="org.freedesktop.DBus",
member="AddMatch",
signature="s",
body=[f"sender={bus1.unique_name}"],
)
)
interface = interface_class("test.interface")
export_path = "/test/path"
bus1.export(export_path, interface)
async def wait_for_message():
# TODO timeout
future = asyncio.get_event_loop().create_future()
def message_handler(signal):
if signal.interface == "org.freedesktop.DBus.Properties":
bus2.remove_message_handler(message_handler)
future.set_result(signal)
bus2.add_message_handler(message_handler)
return await future
bus2.send(
Message(
destination=bus1.unique_name,
interface=interface.name,
path=export_path,
member="do_emit_properties_changed",
)
)
signal = await wait_for_message()
assert signal.interface == "org.freedesktop.DBus.Properties"
assert signal.member == "PropertiesChanged"
assert signal.signature == "sa{sv}as"
assert signal.body == [
interface.name,
{"string_prop": Variant("s", "asdf")},
["container_prop"],
]

View File

@@ -0,0 +1,250 @@
import asyncio
import pytest
from dbus_next import Message, MessageType
from dbus_next.aio import MessageBus
from dbus_next.constants import PropertyAccess
from dbus_next.service import (
ServiceInterface,
SignalDisabledError,
dbus_property,
signal,
)
from dbus_next.signature import Variant
class ExampleInterface(ServiceInterface):
def __init__(self, name):
super().__init__(name)
@signal()
def signal_empty(self):
assert type(self) is ExampleInterface
@signal()
def signal_simple(self) -> "s":
assert type(self) is ExampleInterface
return "hello"
@signal()
def signal_multiple(self) -> "ss":
assert type(self) is ExampleInterface
return ["hello", "world"]
@signal(name="renamed")
def original_name(self):
assert type(self) is ExampleInterface
@signal(disabled=True)
def signal_disabled(self):
assert type(self) is ExampleInterface
@dbus_property(access=PropertyAccess.READ)
def test_prop(self) -> "i":
return 42
class SecondExampleInterface(ServiceInterface):
def __init__(self, name):
super().__init__(name)
@dbus_property(access=PropertyAccess.READ)
def str_prop(self) -> "s":
return "abc"
@dbus_property(access=PropertyAccess.READ)
def list_prop(self) -> "ai":
return [1, 2, 3]
class ExpectMessage:
def __init__(self, bus1, bus2, interface_name, timeout=1):
self.future = asyncio.get_event_loop().create_future()
self.bus1 = bus1
self.bus2 = bus2
self.interface_name = interface_name
self.timeout = timeout
self.timeout_task = None
def message_handler(self, msg):
if msg.sender == self.bus1.unique_name and msg.interface == self.interface_name:
self.timeout_task.cancel()
self.future.set_result(msg)
return True
def timeout_cb(self):
self.future.set_exception(TimeoutError)
async def __aenter__(self):
self.bus2.add_message_handler(self.message_handler)
self.timeout_task = asyncio.get_event_loop().call_later(
self.timeout, self.timeout_cb
)
return self.future
async def __aexit__(self, exc_type, exc_val, exc_tb):
self.bus2.remove_message_handler(self.message_handler)
def assert_signal_ok(signal, export_path, member, signature, body):
assert signal.message_type == MessageType.SIGNAL
assert signal.path == export_path
assert signal.member == member
assert signal.signature == signature
assert signal.body == body
@pytest.mark.asyncio
async def test_signals():
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()
interface = ExampleInterface("test.interface")
export_path = "/test/path"
bus1.export(export_path, interface)
await bus2.call(
Message(
destination="org.freedesktop.DBus",
path="/org/freedesktop/DBus",
interface="org.freedesktop.DBus",
member="AddMatch",
signature="s",
body=[f"sender={bus1.unique_name}"],
)
)
async with ExpectMessage(bus1, bus2, interface.name) as expected_signal:
interface.signal_empty()
assert_signal_ok(
signal=await expected_signal,
export_path=export_path,
member="signal_empty",
signature="",
body=[],
)
async with ExpectMessage(bus1, bus2, interface.name) as expected_signal:
interface.original_name()
assert_signal_ok(
signal=await expected_signal,
export_path=export_path,
member="renamed",
signature="",
body=[],
)
async with ExpectMessage(bus1, bus2, interface.name) as expected_signal:
interface.signal_simple()
assert_signal_ok(
signal=await expected_signal,
export_path=export_path,
member="signal_simple",
signature="s",
body=["hello"],
)
async with ExpectMessage(bus1, bus2, interface.name) as expected_signal:
interface.signal_multiple()
assert_signal_ok(
signal=await expected_signal,
export_path=export_path,
member="signal_multiple",
signature="ss",
body=["hello", "world"],
)
with pytest.raises(SignalDisabledError):
interface.signal_disabled()
@pytest.mark.asyncio
async def test_interface_add_remove_signal():
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()
await bus2.call(
Message(
destination="org.freedesktop.DBus",
path="/org/freedesktop/DBus",
interface="org.freedesktop.DBus",
member="AddMatch",
signature="s",
body=[f"sender={bus1.unique_name}"],
)
)
first_interface = ExampleInterface("test.interface.first")
second_interface = SecondExampleInterface("test.interface.second")
export_path = "/test/path"
# add first interface
async with ExpectMessage(
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
) as expected_signal:
bus1.export(export_path, first_interface)
assert_signal_ok(
signal=await expected_signal,
export_path=export_path,
member="InterfacesAdded",
signature="oa{sa{sv}}",
body=[
export_path,
{"test.interface.first": {"test_prop": Variant("i", 42)}},
],
)
# add second interface
async with ExpectMessage(
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
) as expected_signal:
bus1.export(export_path, second_interface)
assert_signal_ok(
signal=await expected_signal,
export_path=export_path,
member="InterfacesAdded",
signature="oa{sa{sv}}",
body=[
export_path,
{
"test.interface.second": {
"str_prop": Variant("s", "abc"),
"list_prop": Variant("ai", [1, 2, 3]),
}
},
],
)
# remove single interface
async with ExpectMessage(
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
) as expected_signal:
bus1.unexport(export_path, second_interface)
assert_signal_ok(
signal=await expected_signal,
export_path=export_path,
member="InterfacesRemoved",
signature="oas",
body=[export_path, ["test.interface.second"]],
)
# add second interface again
async with ExpectMessage(
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
) as expected_signal:
bus1.export(export_path, second_interface)
await expected_signal
# remove multiple interfaces
async with ExpectMessage(
bus1, bus2, "org.freedesktop.DBus.ObjectManager"
) as expected_signal:
bus1.unexport(export_path)
assert_signal_ok(
signal=await expected_signal,
export_path=export_path,
member="InterfacesRemoved",
signature="oas",
body=[export_path, ["test.interface.first", "test.interface.second"]],
)

View File

@@ -0,0 +1,237 @@
import pytest
from dbus_next import Message, MessageType
from dbus_next import introspection as intr
from dbus_next.aio import MessageBus
from dbus_next.constants import ErrorType
from dbus_next.service import PropertyAccess, ServiceInterface, dbus_property
from dbus_next.signature import Variant
standard_interfaces_count = len(intr.Node.default().interfaces)
class ExampleInterface(ServiceInterface):
def __init__(self, name):
super().__init__(name)
class ExampleComplexInterface(ServiceInterface):
def __init__(self, name):
self._foo = 42
self._bar = "str"
self._async_prop = "async"
super().__init__(name)
@dbus_property(access=PropertyAccess.READ)
def Foo(self) -> "y":
return self._foo
@dbus_property(access=PropertyAccess.READ)
def Bar(self) -> "s":
return self._bar
@dbus_property(access=PropertyAccess.READ)
async def AsyncProp(self) -> "s":
return self._async_prop
@pytest.mark.asyncio
async def test_introspectable_interface():
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()
interface = ExampleInterface("test.interface")
interface2 = ExampleInterface("test.interface2")
export_path = "/test/path"
bus1.export(export_path, interface)
bus1.export(export_path, interface2)
reply = await bus2.call(
Message(
destination=bus1.unique_name,
path=export_path,
interface="org.freedesktop.DBus.Introspectable",
member="Introspect",
)
)
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
assert reply.signature == "s"
node = intr.Node.parse(reply.body[0])
assert len(node.interfaces) == standard_interfaces_count + 2
assert node.interfaces[-1].name == "test.interface2"
assert node.interfaces[-2].name == "test.interface"
assert not node.nodes
# introspect works on every path
reply = await bus2.call(
Message(
destination=bus1.unique_name,
path="/path/doesnt/exist",
interface="org.freedesktop.DBus.Introspectable",
member="Introspect",
)
)
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
assert reply.signature == "s"
node = intr.Node.parse(reply.body[0])
assert not node.interfaces
assert not node.nodes
@pytest.mark.asyncio
async def test_peer_interface():
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()
reply = await bus2.call(
Message(
destination=bus1.unique_name,
path="/path/doesnt/exist",
interface="org.freedesktop.DBus.Peer",
member="Ping",
)
)
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
assert reply.signature == ""
reply = await bus2.call(
Message(
destination=bus1.unique_name,
path="/path/doesnt/exist",
interface="org.freedesktop.DBus.Peer",
member="GetMachineId",
signature="",
)
)
assert reply.message_type == MessageType.METHOD_RETURN, reply.body[0]
assert reply.signature == "s"
@pytest.mark.asyncio
async def test_object_manager():
expected_reply = {
"/test/path/deeper": {
"test.interface2": {
"Bar": Variant("s", "str"),
"Foo": Variant("y", 42),
"AsyncProp": Variant("s", "async"),
}
}
}
reply_ext = {
"/test/path": {
"test.interface1": {},
"test.interface2": {
"Bar": Variant("s", "str"),
"Foo": Variant("y", 42),
"AsyncProp": Variant("s", "async"),
},
}
}
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()
interface = ExampleInterface("test.interface1")
interface2 = ExampleComplexInterface("test.interface2")
export_path = "/test/path"
bus1.export(export_path, interface)
bus1.export(export_path, interface2)
bus1.export(export_path + "/deeper", interface2)
reply_root = await bus2.call(
Message(
destination=bus1.unique_name,
path="/",
interface="org.freedesktop.DBus.ObjectManager",
member="GetManagedObjects",
)
)
reply_level1 = await bus2.call(
Message(
destination=bus1.unique_name,
path=export_path,
interface="org.freedesktop.DBus.ObjectManager",
member="GetManagedObjects",
)
)
reply_level2 = await bus2.call(
Message(
destination=bus1.unique_name,
path=export_path + "/deeper",
interface="org.freedesktop.DBus.ObjectManager",
member="GetManagedObjects",
)
)
assert reply_root.signature == "a{oa{sa{sv}}}"
assert reply_level1.signature == "a{oa{sa{sv}}}"
assert reply_level2.signature == "a{oa{sa{sv}}}"
assert reply_level2.body == [{}]
assert reply_level1.body == [expected_reply]
expected_reply.update(reply_ext)
assert reply_root.body == [expected_reply]
@pytest.mark.asyncio
async def test_standard_interface_properties():
# standard interfaces have no properties, but should still behave correctly
# when you try to call the methods anyway (#49)
bus1 = await MessageBus().connect()
bus2 = await MessageBus().connect()
interface = ExampleInterface("test.interface1")
export_path = "/test/path"
bus1.export(export_path, interface)
for iface in [
"org.freedesktop.DBus.Properties",
"org.freedesktop.DBus.Introspectable",
"org.freedesktop.DBus.Peer",
"org.freedesktop.DBus.ObjectManager",
]:
result = await bus2.call(
Message(
destination=bus1.unique_name,
path=export_path,
interface="org.freedesktop.DBus.Properties",
member="Get",
signature="ss",
body=[iface, "anything"],
)
)
assert result.message_type is MessageType.ERROR
assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value
result = await bus2.call(
Message(
destination=bus1.unique_name,
path=export_path,
interface="org.freedesktop.DBus.Properties",
member="Set",
signature="ssv",
body=[iface, "anything", Variant("s", "new thing")],
)
)
assert result.message_type is MessageType.ERROR
assert result.error_name == ErrorType.UNKNOWN_PROPERTY.value
result = await bus2.call(
Message(
destination=bus1.unique_name,
path=export_path,
interface="org.freedesktop.DBus.Properties",
member="GetAll",
signature="s",
body=[iface],
)
)
assert result.message_type is MessageType.METHOD_RETURN
assert result.body == [{}]

View 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
View 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
View 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
View 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
View 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

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

View 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
View 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
View 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
View 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
View 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