This commit is contained in:
2024-11-29 18:15:30 +00:00
parent 40aade2d8e
commit bc9415586e
5298 changed files with 1938676 additions and 80 deletions

View File

@ -0,0 +1,116 @@
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<!-- GDBus 2.50.3 -->
<node>
<interface name="org.freedesktop.DBus.Properties">
<method name="Get">
<arg type="s" name="interface_name" direction="in"/>
<arg type="s" name="property_name" direction="in"/>
<arg type="v" name="value" direction="out"/>
</method>
<method name="GetAll">
<arg type="s" name="interface_name" direction="in"/>
<arg type="a{sv}" name="properties" direction="out"/>
</method>
<method name="Set">
<arg type="s" name="interface_name" direction="in"/>
<arg type="s" name="property_name" direction="in"/>
<arg type="v" name="value" direction="in"/>
</method>
<signal name="PropertiesChanged">
<arg type="s" name="interface_name"/>
<arg type="a{sv}" name="changed_properties"/>
<arg type="as" name="invalidated_properties"/>
</signal>
</interface>
<interface name="org.freedesktop.DBus.Introspectable">
<method name="Introspect">
<arg type="s" name="xml_data" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.DBus.Peer">
<method name="Ping"/>
<method name="GetMachineId">
<arg type="s" name="machine_uuid" direction="out"/>
</method>
</interface>
<interface name="org.freedesktop.Secret.Service">
<method name="OpenSession">
<arg type="s" name="algorithm" direction="in"/>
<arg type="v" name="input" direction="in"/>
<arg type="v" name="output" direction="out"/>
<arg type="o" name="result" direction="out"/>
</method>
<method name="CreateCollection">
<arg type="a{sv}" name="properties" direction="in"/>
<arg type="s" name="alias" direction="in"/>
<arg type="o" name="collection" direction="out"/>
<arg type="o" name="prompt" direction="out"/>
</method>
<method name="SearchItems">
<arg type="a{ss}" name="attributes" direction="in"/>
<arg type="ao" name="unlocked" direction="out"/>
<arg type="ao" name="locked" direction="out"/>
</method>
<method name="Unlock">
<arg type="ao" name="objects" direction="in"/>
<arg type="ao" name="unlocked" direction="out"/>
<arg type="o" name="prompt" direction="out"/>
</method>
<method name="Lock">
<arg type="ao" name="objects" direction="in"/>
<arg type="ao" name="locked" direction="out"/>
<arg type="o" name="Prompt" direction="out"/>
</method>
<method name="LockService"/>
<method name="ChangeLock">
<arg type="o" name="collection" direction="in"/>
<arg type="o" name="prompt" direction="out"/>
</method>
<method name="GetSecrets">
<arg type="ao" name="items" direction="in"/>
<arg type="o" name="session" direction="in"/>
<arg type="a{o(oayays)}" name="secrets" direction="out"/>
</method>
<method name="ReadAlias">
<arg type="s" name="name" direction="in"/>
<arg type="o" name="collection" direction="out"/>
</method>
<method name="SetAlias">
<arg type="s" name="name" direction="in"/>
<arg type="o" name="collection" direction="in"/>
</method>
<signal name="CollectionCreated">
<arg type="o" name="collection"/>
</signal>
<signal name="CollectionDeleted">
<arg type="o" name="collection"/>
</signal>
<signal name="CollectionChanged">
<arg type="o" name="collection"/>
</signal>
<property type="ao" name="Collections" access="read"/>
</interface>
<interface name="org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface">
<method name="ChangeWithMasterPassword">
<arg type="o" name="collection" direction="in"/>
<arg type="(oayays)" name="original" direction="in"/>
<arg type="(oayays)" name="master" direction="in"/>
</method>
<method name="ChangeWithPrompt">
<arg type="o" name="collection" direction="in"/>
<arg type="o" name="prompt" direction="out"/>
</method>
<method name="CreateWithMasterPassword">
<arg type="a{sv}" name="attributes" direction="in"/>
<arg type="(oayays)" name="master" direction="in"/>
<arg type="o" name="collection" direction="out"/>
</method>
<method name="UnlockWithMasterPassword">
<arg type="o" name="collection" direction="in"/>
<arg type="(oayays)" name="master" direction="in"/>
</method>
</interface>
<node name="session"/>
<node name="collection"/>
</node>

View File

@ -0,0 +1,24 @@
import pytest
from jeepney import auth
def test_make_auth_external():
b = auth.make_auth_external()
assert b.startswith(b'AUTH EXTERNAL')
def test_make_auth_anonymous():
b = auth.make_auth_anonymous()
assert b.startswith(b'AUTH ANONYMOUS')
def test_parser():
p = auth.SASLParser()
p.feed(b'OK 728d62bc2eb394')
assert not p.authenticated
p.feed(b'1ebbb0b42958b1e0d6\r\n')
assert p.authenticated
def test_parser_rejected():
p = auth.SASLParser()
with pytest.raises(auth.AuthenticationError):
p.feed(b'REJECTED EXTERNAL\r\n')
assert not p.authenticated

View File

@ -0,0 +1,28 @@
from io import StringIO
import os.path
from jeepney.low_level import MessageType, HeaderFields
from jeepney.bindgen import code_from_xml
sample_file = os.path.join(os.path.dirname(__file__), 'secrets_introspect.xml')
def test_bindgen():
with open(sample_file) as f:
xml = f.read()
sio = StringIO()
n_interfaces = code_from_xml(xml, path='/org/freedesktop/secrets',
bus_name='org.freedesktop.secrets',
fh=sio)
# 5 interfaces defined, but we ignore Properties, Introspectable, Peer
assert n_interfaces == 2
# Run the generated code, defining the message generator classes.
binding_ns = {}
exec(sio.getvalue(), binding_ns)
Service = binding_ns['Service']
# Check basic functionality of the Service class
assert Service.interface == 'org.freedesktop.Secret.Service'
msg = Service().SearchItems({"service": "foo", "user": "bar"})
assert msg.header.message_type is MessageType.method_call
assert msg.header.fields[HeaderFields.destination] == 'org.freedesktop.secrets'

View File

@ -0,0 +1,24 @@
import pytest
from testpath import modified_env
from jeepney import bus
def test_get_connectable_addresses():
a = list(bus.get_connectable_addresses('unix:path=/run/user/1000/bus'))
assert a == ['/run/user/1000/bus']
a = list(bus.get_connectable_addresses('unix:abstract=/tmp/foo'))
assert a == ['\0/tmp/foo']
with pytest.raises(RuntimeError):
list(bus.get_connectable_addresses('unix:tmpdir=/tmp'))
def test_get_bus():
with modified_env({
'DBUS_SESSION_BUS_ADDRESS':'unix:path=/run/user/1000/bus',
'DBUS_SYSTEM_BUS_ADDRESS': 'unix:path=/var/run/dbus/system_bus_socket'
}):
assert bus.get_bus('SESSION') == '/run/user/1000/bus'
assert bus.get_bus('SYSTEM') == '/var/run/dbus/system_bus_socket'
assert bus.get_bus('unix:path=/run/user/1002/bus') == '/run/user/1002/bus'

View File

@ -0,0 +1,109 @@
from jeepney import DBusAddress, new_signal, new_method_call
from jeepney.bus_messages import MatchRule, message_bus
portal = DBusAddress(
object_path='/org/freedesktop/portal/desktop',
bus_name='org.freedesktop.portal.Desktop',
)
portal_req_iface = portal.with_interface('org.freedesktop.portal.Request')
def test_match_rule_simple():
rule = MatchRule(
type='signal', interface='org.freedesktop.portal.Request',
)
assert rule.matches(new_signal(portal_req_iface, 'Response'))
# Wrong message type
assert not rule.matches(new_method_call(portal_req_iface, 'Boo'))
# Wrong interface
assert not rule.matches(new_signal(
portal.with_interface('org.freedesktop.portal.FileChooser'), 'Response'
))
def test_match_rule_path_namespace():
assert MatchRule(path_namespace='/org/freedesktop/portal').matches(
new_signal(portal_req_iface, 'Response')
)
# Prefix but not a parent in the path hierarchy
assert not MatchRule(path_namespace='/org/freedesktop/por').matches(
new_signal(portal_req_iface, 'Response')
)
def test_match_rule_arg():
rule = MatchRule(type='method_call')
rule.add_arg_condition(0, 'foo')
assert rule.matches(new_method_call(
portal_req_iface, 'Boo', signature='s', body=('foo',)
))
assert not rule.matches(new_method_call(
portal_req_iface, 'Boo', signature='s', body=('foobar',)
))
# No such argument
assert not rule.matches(new_method_call(portal_req_iface, 'Boo'))
def test_match_rule_arg_path():
rule = MatchRule(type='method_call')
rule.add_arg_condition(0, '/aa/bb/', kind='path')
# Exact match
assert rule.matches(new_method_call(
portal_req_iface, 'Boo', signature='s', body=('/aa/bb/',)
))
# Match a prefix
assert rule.matches(new_method_call(
portal_req_iface, 'Boo', signature='s', body=('/aa/bb/cc',)
))
# Argument is a prefix, ending with /
assert rule.matches(new_method_call(
portal_req_iface, 'Boo', signature='s', body=('/aa/',)
))
# Argument is a prefix, but NOT ending with /
assert not rule.matches(new_method_call(
portal_req_iface, 'Boo', signature='s', body=('/aa',)
))
assert not rule.matches(new_method_call(
portal_req_iface, 'Boo', signature='s', body=('/aa/bb',)
))
# Not a string
assert not rule.matches(new_method_call(
portal_req_iface, 'Boo', signature='u', body=(12,)
))
def test_match_rule_arg_namespace():
rule = MatchRule(member='NameOwnerChanged')
rule.add_arg_condition(0, 'com.example.backend1', kind='namespace')
# Exact match
assert rule.matches(new_signal(
message_bus, 'NameOwnerChanged', 's', ('com.example.backend1',)
))
# Parent of the name
assert rule.matches(new_signal(
message_bus, 'NameOwnerChanged', 's', ('com.example.backend1.foo.bar',)
))
# Prefix but not a parent in the namespace
assert not rule.matches(new_signal(
message_bus, 'NameOwnerChanged', 's', ('com.example.backend12',)
))
# Not a string
assert not rule.matches(new_signal(
message_bus, 'NameOwnerChanged', 'u', (1,)
))

View File

@ -0,0 +1,80 @@
import errno
import os
import socket
import pytest
from jeepney import FileDescriptor, NoFDError
def assert_not_fd(fd: int):
"""Check that the given number is not open as a file descriptor"""
with pytest.raises(OSError) as exc_info:
os.stat(fd)
assert exc_info.value.errno == errno.EBADF
def test_close(tmp_path):
fd = os.open(tmp_path / 'a', os.O_CREAT | os.O_RDWR)
with FileDescriptor(fd) as wfd:
assert wfd.fileno() == fd
# Leaving the with block is equivalent to calling .close()
assert 'closed' in repr(wfd)
with pytest.raises(NoFDError):
wfd.fileno()
assert_not_fd(fd)
def test_to_raw_fd(tmp_path):
fd = os.open(tmp_path / 'a', os.O_CREAT)
wfd = FileDescriptor(fd)
assert wfd.fileno() == fd
assert wfd.to_raw_fd() == fd
try:
assert 'converted' in repr(wfd)
with pytest.raises(NoFDError):
wfd.fileno()
finally:
os.close(fd)
def test_to_file(tmp_path):
fd = os.open(tmp_path / 'a', os.O_CREAT | os.O_RDWR)
wfd = FileDescriptor(fd)
with wfd.to_file('w') as f:
assert f.write('abc')
assert 'converted' in repr(wfd)
with pytest.raises(NoFDError):
wfd.fileno()
assert_not_fd(fd) # Check FD was closed by file object
assert (tmp_path / 'a').read_text() == 'abc'
def test_to_socket():
s1, s2 = socket.socketpair()
try:
s1.sendall(b'abcd')
sfd = s2.detach()
wfd = FileDescriptor(sfd)
with wfd.to_socket() as sock:
b = sock.recv(16)
assert b and b'abcd'.startswith(b)
assert 'converted' in repr(wfd)
with pytest.raises(NoFDError):
wfd.fileno()
assert_not_fd(sfd) # Check FD was closed by socket object
finally:
s1.close()

View File

@ -0,0 +1,87 @@
import pytest
from jeepney.low_level import *
HELLO_METHOD_CALL = (
b'l\x01\x00\x01\x00\x00\x00\x00\x01\x00\x00\x00m\x00\x00\x00\x01\x01o\x00\x15'
b'\x00\x00\x00/org/freedesktop/DBus\x00\x00\x00\x02\x01s\x00\x14\x00\x00\x00'
b'org.freedesktop.DBus\x00\x00\x00\x00\x03\x01s\x00\x05\x00\x00\x00Hello\x00'
b'\x00\x00\x06\x01s\x00\x14\x00\x00\x00org.freedesktop.DBus\x00\x00\x00\x00')
def test_parser_simple():
msg = Parser().feed(HELLO_METHOD_CALL)[0]
assert msg.header.fields[HeaderFields.member] == 'Hello'
def chunks(src, size):
pos = 0
while pos < len(src):
end = pos + size
yield src[pos:end]
pos = end
def test_parser_chunks():
p = Parser()
chunked = list(chunks(HELLO_METHOD_CALL, 16))
for c in chunked[:-1]:
assert p.feed(c) == []
msg = p.feed(chunked[-1])[0]
assert msg.header.fields[HeaderFields.member] == 'Hello'
def test_multiple():
msgs = Parser().feed(HELLO_METHOD_CALL * 6)
assert len(msgs) == 6
for msg in msgs:
assert msg.header.fields[HeaderFields.member] == 'Hello'
def test_roundtrip():
msg = Parser().feed(HELLO_METHOD_CALL)[0]
assert msg.serialise() == HELLO_METHOD_CALL
def test_serialise_dict():
data = {
'a': 'b',
'de': 'f',
}
string_type = simple_types['s']
sig = Array(DictEntry([string_type, string_type]))
print(sig.serialise(data, 0, Endianness.little))
assert sig.serialise(data, 0, Endianness.little) == (
b'\x1e\0\0\0' + # Length
b'\0\0\0\0' + # Padding
b'\x01\0\0\0a\0\0\0' +
b'\x01\0\0\0b\0\0\0' +
b'\x02\0\0\0de\0\0' +
b'\x01\0\0\0f\0'
)
def test_parse_signature():
sig = parse_signature(list('(a{sv}(oayays)b)'))
print(sig)
assert sig == Struct([
Array(DictEntry([simple_types['s'], Variant()])),
Struct([
simple_types['o'],
Array(simple_types['y']),
Array(simple_types['y']),
simple_types['s']
]),
simple_types['b'],
])
class fake_list(list):
def __init__(self, n):
super().__init__()
self._n = n
def __len__(self):
return self._n
def __iter__(self):
return iter(range(self._n))
def test_array_limit():
# The spec limits arrays to 64 MiB
a = Array(FixedType(8, 'Q')) # 'at' - array of uint64
a.serialise(fake_list(100), 0, Endianness.little)
with pytest.raises(SizeLimitError):
a.serialise(fake_list(2**23 + 1), 0, Endianness.little)

View File

@ -0,0 +1,32 @@
from asyncio import Future
import pytest
from jeepney.routing import Router
from jeepney.wrappers import new_method_return, new_error, DBusErrorResponse
from jeepney.bus_messages import message_bus
def test_message_reply():
router = Router(Future)
call = message_bus.Hello()
future = router.outgoing(call)
router.incoming(new_method_return(call, 's', ('test',)))
assert future.result() == ('test',)
def test_error():
router = Router(Future)
call = message_bus.Hello()
future = router.outgoing(call)
router.incoming(new_error(call, 'TestError', 'u', (31,)))
with pytest.raises(DBusErrorResponse) as e:
future.result()
assert e.value.name == 'TestError'
assert e.value.data == (31,)
def test_unhandled():
unhandled = []
router = Router(Future, on_unhandled=unhandled.append)
msg = message_bus.Hello()
router.incoming(msg)
assert len(unhandled) == 1
assert unhandled[0] == msg