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()
|
||||
Reference in New Issue
Block a user