From 97d01a1d540e5ec31b752c31ad9f2b794eddf472 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Tue, 26 Sep 2006 20:50:58 +0100 Subject: [PATCH] Throughout dbus-python: Use the C implementation. Add document API_CHANGES.txt listing visible API changes. Add more test cases, for low-level Python <-> D-Bus type mappings. Amend existing test cases to cope with the API changes. --- API_CHANGES.txt | 84 +++++++++++++++++++++++++++++++ dbus/_dbus.py | 56 ++++++--------------- dbus/decorators.py | 13 ++--- dbus/exceptions.py | 3 +- dbus/proxies.py | 90 +++++++++++++++++++++------------ dbus/service.py | 82 +++++++++++++++--------------- dbus/types.py | 34 ++++--------- setup.py | 25 +++------ test/cross-test-client.py | 55 ++++++++++---------- test/cross-test-server.py | 11 +++- test/run-test.sh | 3 +- test/test-client.py | 33 ++++++------ test/test-service.py | 16 ++++-- test/test-standalone.py | 126 ++++++++++++++++++++++++++++++++++++++++++++++ 14 files changed, 417 insertions(+), 214 deletions(-) create mode 100644 API_CHANGES.txt rewrite dbus/types.py (72%) create mode 100755 test/test-standalone.py diff --git a/API_CHANGES.txt b/API_CHANGES.txt new file mode 100644 index 0000000..bffa85f --- /dev/null +++ b/API_CHANGES.txt @@ -0,0 +1,84 @@ +=============================== +API changes in dbus-python-in-c +=============================== + +:Author: Simon McVittie +:Contact: simon.mcvittie@collabora.co.uk +:Organization: `Collabora Ltd`_ +:Date: 2006-09-26 + +.. _ Collabora Ltd: http://www.collabora.co.uk/ + +* Byte is a subtype of str rather than of int, which better matches + Python's conventions for dealing with byte streams. Its constructor + accepts either single-byte strings or integers in the range 0 to 255. + +* In method parameters, method returns from proxy methods, etc., + integers arrive as instances of dbus.Int32 etc., bytes arrive as + Byte, and so on, rather than everything being converted to an + appropriate built-in Python type. This means you can tell exactly + what arguments went over the bus, and their types. + +* Variants arrive as method parameters, returns from proxy methods, etc. + as Variant objects. This is a bit of a big change, but makes things + completely unambiguous. To unwrap variants like the old Pyrex + implementation did, you can write:: + + while isinstance(myparam, dbus.Variant): + myparam = myparam.object + + This should also work (and do nothing) under the Pyrex implementation. + +* The D-Bus integer types (dbus.Int32, etc.) are properly range-checked. + +* Variants are now immutable "value objects". + +* Variants are somewhat more useful: they can be cast using int(), + str(), long(), float(), you can iterate over them with iter() if they + contain an iterable, you can compare them with ``==`` and ``!=``, + and you can hash them if the underlying object supports it. + +* Array constructor takes arguments (iterable[, signature]) + rather than (iterable[, type][, signature]); ditto Variant, Dict + +* Proxy methods with multiple return values return a tuple rather than + a list. + +* Calling a proxy method with reply ignored, or with async + handlers, returns None + +* The Boolean, String, Double and Struct types are now aliases for the + built-in bool, unicode, float and tuple types. Their use is vaguely + deprecated. + +* ConnectionError no longer exists (it was never raised) + +* dbus_bindings is now called _dbus_bindings, and is considerably + different internally: + + * connections are private at the libdbus level: shared connections + are only shared among Python code + + * The MessageIter stuff is now done in C: there's a much simpler + Python API, ``Message.append(...)`` where positional arguments are + the things to be appended, and the keyword argument ``signature`` + controls how objects are interpreted + + * The signature-guessing algorithm used if there is no proper + signature is exposed as a static method, + ``Message.guess_signature(*args)`` + + * Bus is a subclass of Connection rather than being a wrapper object + which has-a Connection + + * Some relatively internal methods have been renamed starting with + an underscore - most Python code shouldn't need to use them, and + they expose the full complexity of Messages etc. + + * The timeouts in _send_with_reply and in _send_with_reply_and_block + are in (possibly fractional) seconds, as is conventional in Python + + * The specialized Message subclasses have names ending with Message + +.. + vim:set sw=2 sts=2 et ft=rst tw=72: diff --git a/dbus/_dbus.py b/dbus/_dbus.py index f82d643..84acf01 100644 --- a/dbus/_dbus.py +++ b/dbus/_dbus.py @@ -41,8 +41,8 @@ For example, the dbus-daemon itself provides a service and some objects:: """ __all__ = ('Bus', 'SystemBus', 'SessionBus', 'StarterBus', 'Interface', - # From exceptions (some originally from _dbus_bindings) - 'DBusException', 'ConnectionError', 'MissingErrorHandlerException', + # From exceptions (DBusException originally from _dbus_bindings) + 'DBusException', 'MissingErrorHandlerException', 'MissingReplyHandlerException', 'ValidationException', 'IntrospectionParserException', 'UnknownMethodException', 'NameExistsException', @@ -58,7 +58,7 @@ from proxies import * from exceptions import * from matchrules import * -class Bus(object): +class Bus(_dbus_bindings._Bus): """A connection to a DBus daemon. One of three possible standard buses, the SESSION, SYSTEM, @@ -111,17 +111,13 @@ class Bus(object): else: raise ValueError('invalid bus_type %s' % bus_type) - bus = object.__new__(subclass) + bus = _dbus_bindings._Bus.__new__(subclass, bus_type) bus._bus_type = bus_type bus._bus_names = weakref.WeakValueDictionary() bus._match_rule_tree = SignalMatchTree() - # FIXME: if you get a starter and a system/session bus connection - # in the same process, it's the same underlying connection that - # is returned by bus_get, but we initialise it twice - bus._connection = _dbus_bindings.bus_get(bus_type, private) - bus._connection.add_filter(bus._signal_func) + bus._add_filter(bus._signal_func) if use_default_mainloop: func = getattr(dbus, "_dbus_mainloop_setup_function", None) @@ -138,13 +134,10 @@ class Bus(object): # same object if __new__ returns a shared instance pass - def close(self): - """Close the connection.""" - self._connection.close() - def get_connection(self): - """Return the underlying `_dbus_bindings.Connection`.""" - return self._connection + return self + + _connection = property(get_connection) def get_session(private=False): """Static method that returns a connection to the session bus. @@ -277,7 +270,7 @@ class Bus(object): self._match_rule_tree.add(match_rule) - _dbus_bindings.bus_add_match(self._connection, repr(match_rule)) + self.add_match_string(repr(match_rule)) def remove_signal_receiver(self, handler_function, signal_name=None, @@ -302,19 +295,13 @@ class Bus(object): self._match_rule_tree.remove(match_rule) - #TODO we leak match rules in the lower level bindings. We need to ref count them + # TODO: remove the match string at the libdbus level - def get_unix_user(self, named_service): - """Get the numeric uid of the process which owns the given bus name - on the connected bus daemon. - - :Parameters: - `named_service` : str - A bus name (may be either a unique name or a well-known name) - """ - return _dbus_bindings.bus_get_unix_user(self._connection, named_service) - def _signal_func(self, connection, message): + """D-Bus filter function. Handle signals by dispatching to Python + callbacks kept in the match-rule tree. + """ + if (message.get_type() != _dbus_bindings.MESSAGE_TYPE_SIGNAL): return _dbus_bindings.HANDLER_RESULT_NOT_YET_HANDLED @@ -327,21 +314,6 @@ class Bus(object): self._match_rule_tree.exec_matches(match_rule, message) - def start_service_by_name(self, named_service): - """Start a service which will implement the given bus name on this - Bus. - - :Parameters: - `named_service` : str - The well-known bus name for which an implementation is required - - :Returns: A tuple of 2 elements. The first is always True, the second is - either START_REPLY_SUCCESS or START_REPLY_ALREADY_RUNNING. - - :Raises DBusException: if the service could not be started. - """ - return _dbus_bindings.bus_start_service_by_name(self._connection, named_service) - def __repr__(self): if self._bus_type == self.TYPE_SESSION: name = 'SESSION' diff --git a/dbus/decorators.py b/dbus/decorators.py index 6dc2176..5486bb7 100644 --- a/dbus/decorators.py +++ b/dbus/decorators.py @@ -104,18 +104,15 @@ def signal(dbus_interface, signature=None): def decorator(func): def emit_signal(self, *args, **keywords): func(self, *args, **keywords) - message = _dbus_bindings.Signal(self._object_path, dbus_interface, func.__name__) - iter = message.get_iter(True) + message = _dbus_bindings.SignalMessage(self._object_path, dbus_interface, func.__name__) if emit_signal._dbus_signature: - signature = tuple(_dbus_bindings.Signature(emit_signal._dbus_signature)) - for (arg, sig) in zip(args, signature): - iter.append_strict(arg, sig) + message.append(signature=emit_signal._dbus_signature, + *args) else: - for arg in args: - iter.append(arg) + message.append(*args) - self._connection.send(message) + self._connection._send(message) args = inspect.getargspec(func)[0] args.pop(0) diff --git a/dbus/exceptions.py b/dbus/exceptions.py index 60ba3fd..801ede6 100644 --- a/dbus/exceptions.py +++ b/dbus/exceptions.py @@ -1,6 +1,6 @@ """D-Bus exceptions.""" -__all__ = ('DBusException', 'ConnectionError', 'MissingErrorHandlerException', +__all__ = ('DBusException', 'MissingErrorHandlerException', 'MissingReplyHandlerException', 'ValidationException', 'IntrospectionParserException', 'UnknownMethodException', 'NameExistsException') @@ -8,7 +8,6 @@ __all__ = ('DBusException', 'ConnectionError', 'MissingErrorHandlerException', import _dbus_bindings DBusException = _dbus_bindings.DBusException -ConnectionError = _dbus_bindings.ConnectionError class MissingErrorHandlerException(DBusException): def __init__(self): diff --git a/dbus/proxies.py b/dbus/proxies.py index d9cfb2a..9c06485 100644 --- a/dbus/proxies.py +++ b/dbus/proxies.py @@ -1,11 +1,36 @@ -import _dbus_bindings -import introspect_parser import sys -from exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException +import logging + +import _dbus_bindings +import dbus.introspect_parser as introspect_parser +from dbus.exceptions import MissingReplyHandlerException, MissingErrorHandlerException, IntrospectionParserException, DBusException __docformat__ = 'restructuredtext' +_logger = logging.getLogger('dbus.proxies') + + +class _ReplyHandler(object): + __slots__ = ('_on_error', '_on_reply') + def __init__(self, on_reply, on_error): + self._on_error = on_error + self._on_reply = on_reply + + def __call__(self, message): + if isinstance(message, _dbus_bindings.MethodReturnMessage): + self._on_reply(*message.get_args_list()) + elif isinstance(message, _dbus_bindings.ErrorMessage): + args = message.get_args_list() + if len(args) > 0: + self._on_error(DBusException(args[0])) + else: + self._on_error(DBusException()) + else: + self._on_error(DBusException('Unexpected reply message type: %s' + % message)) + + class DeferedMethod: """A DeferedMethod @@ -24,7 +49,7 @@ class DeferedMethod: #block for now even on async # FIXME: put ret in async queue in future if we have a reply handler - self._proxy_method._proxy._pending_introspect.block() + self._proxy_method._proxy._pending_introspect._block() ret = self._proxy_method (*args, **keywords) return ret @@ -82,35 +107,38 @@ class ProxyMethod: if self._proxy._introspect_method_map.has_key (key): introspect_sig = self._proxy._introspect_method_map[key] - message = _dbus_bindings.MethodCall(self._object_path, dbus_interface, self._method_name) + message = _dbus_bindings.MethodCallMessage(destination=None, + path=self._object_path, + interface=dbus_interface, + method=self._method_name) message.set_destination(self._named_service) # Add the arguments to the function - iter = message.get_iter(True) - - if introspect_sig: - for (arg, sig) in zip(args, _dbus_bindings.Signature(introspect_sig)): - iter.append_strict(arg, sig) - else: - for arg in args: - iter.append(arg) - + try: + message.append(signature=introspect_sig, *args) + except Exception, e: + _logger.error('Unable to set arguments %r according to ' + 'introspected signature %r: %s: %s', + args, introspect_sig, e.__class__, e) + raise + + # FIXME: using private API on Connection while I decide whether to + # make it public or what if ignore_reply: - result = self._connection.send(message) - args_tuple = (result,) + result = self._connection._send(message) + return None elif reply_handler: - result = self._connection.send_with_reply_handlers(message, timeout, reply_handler, error_handler) - args_tuple = result - else: - reply_message = self._connection.send_with_reply_and_block(message, timeout) - args_tuple = reply_message.get_args_list() - - if len(args_tuple) == 0: - return - elif len(args_tuple) == 1: - return args_tuple[0] + result = self._connection._send_with_reply(message, _ReplyHandler(reply_handler, error_handler), timeout/1000.0) + return None else: - return args_tuple + reply_message = self._connection._send_with_reply_and_block(message, timeout) + args_list = reply_message.get_args_list() + if len(args_list) == 0: + return None + elif len(args_list) == 1: + return args_list[0] + else: + return tuple(args_list) class ProxyObject: @@ -157,7 +185,7 @@ class ProxyObject: else: self._introspect_state = self.INTROSPECT_STATE_INTROSPECT_IN_PROGRESS - (result, self._pending_introspect) = self._Introspect() + self._pending_introspect = self._Introspect() def connect_to_signal(self, signal_name, handler_function, dbus_interface=None, **keywords): @@ -194,12 +222,10 @@ class ProxyObject: **keywords) def _Introspect(self): - message = _dbus_bindings.MethodCall(self._object_path, 'org.freedesktop.DBus.Introspectable', 'Introspect') + message = _dbus_bindings.MethodCallMessage(None, self._object_path, 'org.freedesktop.DBus.Introspectable', 'Introspect') message.set_destination(self._named_service) - result = self._bus.get_connection().send_with_reply_handlers(message, -1, - self._introspect_reply_handler, - self._introspect_error_handler) + result = self._bus.get_connection()._send_with_reply(message, _ReplyHandler(self._introspect_reply_handler, self._introspect_error_handler), -1) return result def _introspect_execute_queue(self): diff --git a/dbus/service.py b/dbus/service.py index 32a3a28..75c0c1a 100644 --- a/dbus/service.py +++ b/dbus/service.py @@ -1,15 +1,21 @@ __all__ = ('BusName', 'Object', 'method', 'signal') __docformat__ = 'restructuredtext' -import _dbus_bindings -import _dbus +import sys +import logging import operator import traceback -from exceptions import NameExistsException -from exceptions import UnknownMethodException -from decorators import method -from decorators import signal +import _dbus_bindings +import dbus._dbus as _dbus +from dbus.exceptions import NameExistsException +from dbus.exceptions import UnknownMethodException +from dbus.decorators import method +from dbus.decorators import signal + + +_logger = logging.getLogger('dbus.service') + class _VariantSignature(object): """A fake method signature which, when iterated, yields an endless stream @@ -77,7 +83,7 @@ class BusName(object): _dbus_bindings.NAME_FLAG_REPLACE_EXISTING * replace_existing + \ _dbus_bindings.NAME_FLAG_DO_NOT_QUEUE * do_not_queue - retval = _dbus_bindings.bus_request_name(bus.get_connection(), name, name_flags) + retval = bus.request_name(name, name_flags) # TODO: more intelligent tracking of bus name states? if retval == _dbus_bindings.REQUEST_NAME_REPLY_PRIMARY_OWNER: @@ -115,7 +121,7 @@ class BusName(object): # we can delete the low-level name here because these objects # are guaranteed to exist only once for each bus name def __del__(self): - _dbus_bindings.bus_release_name(self._bus.get_connection(), self._name) + self._bus.release_name(self._name) pass def get_bus(self): @@ -197,28 +203,25 @@ def _method_lookup(self, method_name, dbus_interface): raise UnknownMethodException('%s is not a valid method' % method_name) +# FIXME: if signature is '', we used to accept anything, but now insist on +# zero args. Which was desired? -smcv def _method_reply_return(connection, message, method_name, signature, *retval): - reply = _dbus_bindings.MethodReturn(message) - iter = reply.get_iter(append=True) - - # do strict adding if an output signature was provided - if signature: - if len(signature) > len(retval): - raise TypeError('output signature %s is longer than the number of values returned by %s' % - (signature, method_name)) - elif len(retval) > len(signature): - raise TypeError('output signature %s is shorter than the number of values returned by %s' % - (signature, method_name)) - else: - for (value, sig) in zip(retval, signature): - iter.append_strict(value, sig) - - # no signature, try and guess the return type by inspection - else: - for value in retval: - iter.append(value) - - connection.send(reply) + reply = _dbus_bindings.MethodReturnMessage(message) + try: + reply.append(signature=signature, *retval) + except Exception, e: + if signature is None: + try: + signature = reply.guess_signature(retval) + ' (guessed)' + except Exception, e: + _logger.error('Unable to guess signature for arguments %r: ' + '%s: %s', retval, e.__class__, e) + raise + _logger.error('Unable to append %r to message with signature %s: ' + '%s: %s', retval, signature, e.__class__, e) + raise + + connection._send(reply) def _method_reply_error(connection, message, exception): @@ -230,9 +233,9 @@ def _method_reply_error(connection, message, exception): name = 'org.freedesktop.DBus.Python.%s.%s' % (exception.__module__, exception.__class__.__name__) contents = traceback.format_exc() - reply = _dbus_bindings.Error(message, name, contents) + reply = _dbus_bindings.ErrorMessage(message, name, contents) - connection.send(reply) + connection._send(reply) class InterfaceType(type): @@ -342,10 +345,10 @@ class Object(Interface): self._connection = self._bus.get_connection() - self._connection.register_object_path(object_path, self._unregister_cb, self._message_cb) + self._connection._register_object_path(object_path, self._message_cb, self._unregister_cb) def _unregister_cb(self, connection): - print ("Unregister") + _logger.info('Unregistering exported object %r', self) def _message_cb(self, connection, message): try: @@ -358,9 +361,8 @@ class Object(Interface): args = message.get_args_list() keywords = {} - # iterate signature into list of complete types - if parent_method._dbus_out_signature: - signature = tuple(_dbus_bindings.Signature(parent_method._dbus_out_signature)) + if parent_method._dbus_out_signature is not None: + signature = _dbus_bindings.Signature(parent_method._dbus_out_signature) else: signature = None @@ -384,17 +386,18 @@ class Object(Interface): # otherwise we send the return values in a reply. if we have a # signature, use it to turn the return value into a tuple as # appropriate - if parent_method._dbus_out_signature: + if signature is not None: + signature_tuple = tuple(signature) # if we have zero or one return values we want make a tuple # for the _method_reply_return function, otherwise we need # to check we're passing it a sequence - if len(signature) == 0: + if len(signature_tuple) == 0: if retval == None: retval = () else: raise TypeError('%s has an empty output signature but did not return None' % method_name) - elif len(signature) == 1: + elif len(signature_tuple) == 1: retval = (retval,) else: if operator.isSequenceType(retval): @@ -406,7 +409,6 @@ class Object(Interface): # no signature, so just turn the return into a tuple and send it as normal else: - signature = None if retval == None: retval = () else: diff --git a/dbus/types.py b/dbus/types.py dissimilarity index 72% index 0e04f42..8be55d7 100644 --- a/dbus/types.py +++ b/dbus/types.py @@ -1,23 +1,11 @@ -__all__ = ('ObjectPath', 'ByteArray', 'Signature', 'Byte', 'Boolean', - 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64', - 'Double', 'String', 'Array', 'Struct', 'Dictionary', 'Variant') - -import _dbus_bindings - -ObjectPath = _dbus_bindings.ObjectPath -ByteArray = _dbus_bindings.ByteArray -Signature = _dbus_bindings.Signature -Byte = _dbus_bindings.Byte -Boolean = _dbus_bindings.Boolean -Int16 = _dbus_bindings.Int16 -UInt16 = _dbus_bindings.UInt16 -Int32 = _dbus_bindings.Int32 -UInt32 = _dbus_bindings.UInt32 -Int64 = _dbus_bindings.Int64 -UInt64 = _dbus_bindings.UInt64 -Double = _dbus_bindings.Double -String = _dbus_bindings.String -Array = _dbus_bindings.Array -Struct = _dbus_bindings.Struct -Dictionary = _dbus_bindings.Dictionary -Variant = _dbus_bindings.Variant +__all__ = ('ObjectPath', 'ByteArray', 'Signature', 'Byte', 'Boolean', + 'Int16', 'UInt16', 'Int32', 'UInt32', 'Int64', 'UInt64', + 'Double', 'String', 'Array', 'Struct', 'Dictionary', 'Variant') + +from _dbus_bindings import ObjectPath, ByteArray, Signature, Byte,\ + Int16, UInt16, Int32, UInt32,\ + Int64, UInt64, Variant, Dictionary, Array +Boolean = bool +String = unicode +Double = float +Struct = tuple diff --git a/setup.py b/setup.py index a295c6a..084eb57 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,6 @@ sys.path.append("dbus") from distutils.core import setup from distutils.extension import Extension from distutils.command.clean import clean -from Pyrex.Distutils import build_ext import extract @@ -20,15 +19,11 @@ def remove(filename): class full_clean(clean): def run(self): clean.run(self) - remove("dbus/extract.pyo") - remove("dbus/_dbus_bindings.pxd") - remove("dbus/_dbus_bindings.c") - remove("dbus/_dbus_glib_bindings.c") remove("ChangeLog") includedirs_flag = ['-I.'] -dbus_includes = ['.'] -dbus_glib_includes = ['.'] +dbus_includes = ['.', 'include'] +dbus_glib_includes = ['.', 'include'] pipe = os.popen3("pkg-config --cflags dbus-1") output = pipe[1].read().strip() @@ -102,11 +97,6 @@ if error: raise SystemExit dbus_glib_libs.extend([ x.replace("-L", "") for x in output.split() ]) -output = open("dbus/_dbus_bindings.pxd", 'w') -includedirs_flag.append('-Idbus/') -extract.main("dbus/_dbus_bindings.pxd.in", includedirs_flag, output) -output.close() - long_desc = '''D-BUS is a message bus system, a simple way for applications to talk to one another. @@ -143,22 +133,19 @@ setup( "dbus/_util", ], ext_modules=[ - Extension("_dbus_bindings", ["dbus/_dbus_bindings.pyx"], + Extension("_dbus_bindings", ["_dbus_bindings/module.c"], + extra_compile_args=['-O0'], include_dirs=dbus_includes, library_dirs=dbus_libs, libraries=["dbus-1"], - ), - Extension("_dbus_glib_bindings", ["dbus/_dbus_glib_bindings.pyx"], + Extension("_dbus_glib_bindings", ["_dbus_glib_bindings/module.c"], include_dirs=dbus_glib_includes, library_dirs=dbus_glib_libs, libraries=["dbus-glib-1", "dbus-1", "glib-2.0"], - define_macros=[ - ('DBUS_API_SUBJECT_TO_CHANGE', '1') - ], ), ], - cmdclass={'build_ext': build_ext, 'clean': full_clean, 'check': dbus_python_check} + cmdclass={'clean': full_clean, 'check': dbus_python_check} ) # vim:ts=4:sw=4:tw=80:si:ai:showmatch:et diff --git a/test/cross-test-client.py b/test/cross-test-client.py index 560561b..ae88f53 100644 --- a/test/cross-test-client.py +++ b/test/cross-test-client.py @@ -1,10 +1,11 @@ import sys from sets import Set from time import sleep +import logging import gobject -from dbus import SessionBus, Interface, Array, Byte, Double, Variant, Boolean, ByteArray +from dbus import SessionBus, Interface, Array, Byte, Double, Variant, Boolean, ByteArray, Int32 from dbus.service import BusName import dbus.glib @@ -14,6 +15,9 @@ from crosstest import CROSS_TEST_PATH, CROSS_TEST_BUS_NAME,\ SignalTestsImpl +logging.basicConfig() + + class Client(SignalTestsImpl): fail_id = 0 expected = Set() @@ -22,7 +26,7 @@ class Client(SignalTestsImpl): for x in self.expected: self.fail_id += 1 print "<%s> fail %d" % (x, self.fail_id) - print "report <%d>: reply to %s didn't arrive" % (self.fail_id, x) + print "report %d: reply to %s didn't arrive" % (self.fail_id, x) sys.stderr.write("CLIENT: asking server to Exit\n") Interface(self.obj, INTERFACE_TESTS).Exit(reply_handler=self.quit_reply_handler, error_handler=self.quit_error_handler) # if the server doesn't reply we'll just exit anyway @@ -44,7 +48,7 @@ class Client(SignalTestsImpl): if (input1, input2) != (42, 23): self.fail_id += 1 print "<%s.Trigger> fail %d" % (INTERFACE_SIGNAL_TESTS, self.fail_id) - print ("report <%d>: expected (42,23), got %r" + print ("report %d: expected (42,23), got %r" % (self.fail_id, (input1, input2))) else: print "<%s.Trigger> pass" % INTERFACE_SIGNAL_TESTS @@ -58,13 +62,14 @@ class Client(SignalTestsImpl): except Exception, e: self.fail_id += 1 print "<%s.%s> fail %d" % (interface, member, self.fail_id) - print ("report <%d>: %s.%s%r: expected %r, raised %r \"%s\"" + print ("report %d: %s.%s%r: expected %r, raised %r \"%s\"" % (self.fail_id, interface, member, args, ret, e, e)) + __import__('traceback').print_exc() return if real_ret != ret: self.fail_id += 1 print "<%s.%s> fail %d" % (interface, member, self.fail_id) - print ("report <%d>: %s.%s%r: expected %r, got %r" + print ("report %d: %s.%s%r: expected %r, got %r" % (self.fail_id, interface, member, args, ret, real_ret)) return print "<%s.%s> pass" % (interface, member) @@ -77,12 +82,12 @@ class Client(SignalTestsImpl): if sender_path != '/Where/Ever': self.fail_id += 1 print "<%s.Trigger> fail %d" % (INTERFACE_TESTS, self.fail_id) - print ("report <%d>: expected signal from /Where/Ever, got %r" + print ("report %d: expected signal from /Where/Ever, got %r" % (self.fail_id, sender_path)) elif param != 42: self.fail_id += 1 print "<%s.Trigger> fail %d" % (INTERFACE_TESTS, self.fail_id) - print ("report <%d>: expected signal param 42, got %r" + print ("report %d: expected signal param 42, got %r" % (self.fail_id, parameter)) else: print "<%s.Trigger> pass" % INTERFACE_TESTS @@ -123,20 +128,18 @@ class Client(SignalTestsImpl): def run_synchronous_tests(self, obj): # "Single tests" self.assert_method_eq(INTERFACE_SINGLE_TESTS, 6, 'Sum', [1, 2, 3]) - # FIXME: works, but should it? self.assert_method_eq(INTERFACE_SINGLE_TESTS, 6, 'Sum', ['\x01', '\x02', '\x03']) self.assert_method_eq(INTERFACE_SINGLE_TESTS, 6, 'Sum', [Byte(1), Byte(2), Byte(3)]) # Main tests - self.assert_method_eq(INTERFACE_TESTS, 'foo', 'Identity', 'foo') - # FIXME: Arrays of Byte -> lists of str, but Byte -> int?! - self.assert_method_eq(INTERFACE_TESTS, 42, 'Identity', Byte(42)) - self.assert_method_eq(INTERFACE_TESTS, 42, 'Identity', Variant(Byte(42))) - self.assert_method_eq(INTERFACE_TESTS, 42, 'Identity', Variant(Variant(Variant(Variant(Variant(Variant(Variant(Byte(42))))))))) - self.assert_method_eq(INTERFACE_TESTS, 42.5, 'Identity', Double(42.5)) - self.assert_method_eq(INTERFACE_TESTS, -42.5, 'Identity', -42.5) - self.assert_method_eq(INTERFACE_TESTS, 0x42, 'IdentityByte', '\x42') - self.assert_method_eq(INTERFACE_TESTS, 42, 'IdentityByte', Byte(42)) + self.assert_method_eq(INTERFACE_TESTS, Variant(u'foo', 's'), 'Identity', 'foo') + self.assert_method_eq(INTERFACE_TESTS, Variant(Byte(42)), 'Identity', Byte(42)) + self.assert_method_eq(INTERFACE_TESTS, Variant(Byte(42)), 'Identity', Variant(Byte(42))) + self.assert_method_eq(INTERFACE_TESTS, Variant(Variant(Variant(Variant(Variant(Variant(Variant(Byte(42)))))))), 'Identity', Variant(Variant(Variant(Variant(Variant(Variant(Variant(Byte(42))))))))) + self.assert_method_eq(INTERFACE_TESTS, Variant(42.5), 'Identity', Double(42.5)) + self.assert_method_eq(INTERFACE_TESTS, Variant(-42.5), 'Identity', -42.5) + self.assert_method_eq(INTERFACE_TESTS, Byte(0x42), 'IdentityByte', '\x42') + self.assert_method_eq(INTERFACE_TESTS, Byte(42), 'IdentityByte', Byte(42)) self.assert_method_eq(INTERFACE_TESTS, True, 'IdentityBool', 42) self.assert_method_eq(INTERFACE_TESTS, True, 'IdentityBool', Boolean(42)) self.assert_method_eq(INTERFACE_TESTS, 42, 'IdentityInt16', 42) @@ -147,12 +150,11 @@ class Client(SignalTestsImpl): self.assert_method_eq(INTERFACE_TESTS, 42, 'IdentityUInt64', 42) self.assert_method_eq(INTERFACE_TESTS, 42.3, 'IdentityDouble', 42.3) self.assert_method_eq(INTERFACE_TESTS, u'\xa9', 'IdentityString', u'\xa9') - # FIXME: if this is the intended API, then Byte shouldn't subclass int - self.assert_method_eq(INTERFACE_TESTS, ['\x01','\x02','\x03'], 'IdentityArray', ByteArray('\x01\x02\x03')) - self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityArray', [1,2,3]) - self.assert_method_eq(INTERFACE_TESTS, ['a','b','c'], 'IdentityArray', ['a','b','c']) - self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityByteArray', ByteArray('\x01\x02\x03')) - self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityByteArray', ['\x01', '\x02', '\x03']) + self.assert_method_eq(INTERFACE_TESTS, [Variant(Byte('\x01')),Variant(Byte('\x02')),Variant(Byte('\x03'))], 'IdentityArray', ByteArray('\x01\x02\x03')) + self.assert_method_eq(INTERFACE_TESTS, [Variant(Int32(1)),Variant(Int32(2)),Variant(Int32(3))], 'IdentityArray', [1,2,3]) + self.assert_method_eq(INTERFACE_TESTS, [Variant(u'a'),Variant(u'b'),Variant(u'c')], 'IdentityArray', ['a','b','c']) + self.assert_method_eq(INTERFACE_TESTS, ['\x01','\x02','\x03'], 'IdentityByteArray', ByteArray('\x01\x02\x03')) + self.assert_method_eq(INTERFACE_TESTS, ['\x01','\x02','\x03'], 'IdentityByteArray', ['\x01', '\x02', '\x03']) self.assert_method_eq(INTERFACE_TESTS, [False,True,True], 'IdentityBoolArray', [0,1,2]) self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityInt16Array', [1,2,3]) self.assert_method_eq(INTERFACE_TESTS, [1,2,3], 'IdentityUInt16Array', [1,2,3]) @@ -165,10 +167,9 @@ class Client(SignalTestsImpl): self.assert_method_eq(INTERFACE_TESTS, 6, 'Sum', [1,2,3]) self.assert_method_eq(INTERFACE_TESTS, {'fps': ['unreal', 'quake'], 'rts': ['warcraft']}, 'InvertMapping', {'unreal': 'fps', 'quake': 'fps', 'warcraft': 'rts'}) - # FIXME: for Pythonicness, this should really return a tuple - self.assert_method_eq(INTERFACE_TESTS, ['a', 1, 2], 'DeStruct', ('a', 1, 2)) - self.assert_method_eq(INTERFACE_TESTS, ['x'], 'Primitize', [Variant(Variant(['x']))]) - self.assert_method_eq(INTERFACE_TESTS, ['x', 1, 2], 'Primitize', [Variant([Variant(['x']), Array([Byte(1), Byte(2)])])]) + self.assert_method_eq(INTERFACE_TESTS, ('a', 1, 2), 'DeStruct', ('a', 1, 2)) + self.assert_method_eq(INTERFACE_TESTS, [Variant('x')], 'Primitize', [Variant(Variant(['x']))]) + self.assert_method_eq(INTERFACE_TESTS, [Variant('x'), Variant(Byte(1)), Variant(Byte(2))], 'Primitize', [Variant([Variant(['x']), Array([Byte(1), Byte(2)])])]) self.assert_method_eq(INTERFACE_TESTS, False, 'Invert', 42) self.assert_method_eq(INTERFACE_TESTS, True, 'Invert', 0) diff --git a/test/cross-test-server.py b/test/cross-test-server.py index 9931c8f..88c43d3 100644 --- a/test/cross-test-server.py +++ b/test/cross-test-server.py @@ -1,5 +1,6 @@ import sys from sets import Set +import logging import gobject @@ -13,6 +14,9 @@ from crosstest import CROSS_TEST_PATH, CROSS_TEST_BUS_NAME, \ SignalTestsImpl +logging.basicConfig() + + class VerboseSet(Set): def add(self, thing): print '<%s> ok' % thing @@ -63,7 +67,7 @@ class SingleTestsImpl(dbus.service.Object): @dbus.service.method(INTERFACE_SINGLE_TESTS, 'ay', 'u') def Sum(self, input): tested_things.add(INTERFACE_SINGLE_TESTS + '.Sum') - return sum(input) + return sum(map(ord, input)) class TestsImpl(dbus.service.Object): @@ -219,7 +223,10 @@ class TestsImpl(dbus.service.Object): for y in self.primitivize_helper(x): yield y for y in self.primitivize_helper(input[x]): - yield input[x] + yield y + elif isinstance(input, dbus.Variant): + for x in self.primitivize_helper(input()): + yield x else: yield input diff --git a/test/run-test.sh b/test/run-test.sh index c8cd276..d592309 100755 --- a/test/run-test.sh +++ b/test/run-test.sh @@ -32,6 +32,7 @@ if test -z "$DBUS_TEST_PYTHON_IN_RUN_TEST"; then export DBUS_TEST_PYTHON_IN_RUN_TEST exec tools/run-with-tmp-session-bus.sh $SCRIPTNAME $MODE fi +echo "running test-standalone.py" +test/test-standalone.py || die "test-standalone.py failed" echo "running test-client.py" test/test-client.py || die "test-client.py failed" - diff --git a/test/test-client.py b/test/test-client.py index 7e91dc1..831dcce 100755 --- a/test/test-client.py +++ b/test/test-client.py @@ -3,6 +3,7 @@ import sys import os import unittest import time +import logging builddir = os.environ["DBUS_TOP_BUILDDIR"] pydir = builddir @@ -16,6 +17,10 @@ import gobject import dbus.glib import dbus.service + +logging.basicConfig() + + pkg = dbus.__file__ if not pkg.startswith(pydir): raise Exception("DBus modules (%s) are not being picked up from the package"%pkg) @@ -32,7 +37,7 @@ test_types_vals = [1, 12323231, 3.14159265, 99999999.99, {1:"a", 2:"b"}, {"a":1, "b":2}, #{"a":(1,"B")}, {1:1.1, 2:2.2}, [[1,2,3],[2,3,4]], [["a","b"],["c","d"]], True, False, - dbus.Int16(-10), dbus.UInt16(10), + dbus.Int16(-10), dbus.UInt16(10), 'SENTINEL', #([1,2,3],"c", 1.2, ["a","b","c"], {"a": (1,"v"), "b": (2,"d")}) ] @@ -62,7 +67,7 @@ class TestDBusBindings(unittest.TestCase): for send_val in test_types_vals: print "Testing %s"% str(send_val) recv_val = self.iface.Echo(send_val) - self.assertEquals(send_val, recv_val) + self.assertEquals(send_val, recv_val.object) def testBenchmarkIntrospect(self): print "\n********* Benchmark Introspect ************" @@ -91,7 +96,7 @@ class TestDBusBindings(unittest.TestCase): if self.do_exit: main_loop.quit() - self.test_controler.assertEquals(val, self.expected_result) + self.test_controler.assertEquals(val.object, self.expected_result) except Exception, e: print "%s:\n%s" % (e.__class__, e) @@ -100,7 +105,8 @@ class TestDBusBindings(unittest.TestCase): if self.do_exit: main_loop.quit() - self.test_controler.assert_(val, False) + self.test_controler.assert_(False, '%s: %s' % (error.__class__, + error)) last_type = test_types_vals[-1] for send_val in test_types_vals: @@ -123,7 +129,7 @@ class TestDBusBindings(unittest.TestCase): values = ["", ("",""), ("","",""), [], {}, ["",""], ["","",""]] methods = [ (self.iface.ReturnOneString, 'SignalOneString', set([0]), set([0])), - (self.iface.ReturnTwoStrings, 'SignalTwoStrings', set([1, 5]), set([5])), + (self.iface.ReturnTwoStrings, 'SignalTwoStrings', set([1, 5]), set([1])), (self.iface.ReturnStruct, 'SignalStruct', set([1, 5]), set([1])), # all of our test values are sequences so will marshall correctly into an array :P (self.iface.ReturnArray, 'SignalArray', set(range(len(values))), set([3, 5, 6])), @@ -136,7 +142,7 @@ class TestDBusBindings(unittest.TestCase): try: ret = method(value) except Exception, e: - print "%s(%r) raised %s" % (method._method_name, values[value], e.__class__) + print "%s(%r) raised %s: %s" % (method._method_name, values[value], e.__class__, e) # should fail if it tried to marshal the wrong type self.assert_(value not in success_values, "%s should succeed when we ask it to return %r\n%s\n%s" % (method._method_name, values[value], e.__class__, e)) @@ -148,7 +154,7 @@ class TestDBusBindings(unittest.TestCase): # check the value is right too :D returns = map(lambda n: values[n], return_values) - self.assert_(ret in returns, "%s should return one of %r" % (method._method_name, returns)) + self.assert_(ret in returns, "%s should return one of %r but it returned %r instead" % (method._method_name, returns, ret)) print "\nTrying correct emission of", signal for value in range(len(values)): @@ -183,12 +189,11 @@ class TestDBusBindings(unittest.TestCase): print "calling AsynchronousMethod with %s %s %s" % (async, fail, val) ret = self.iface.AsynchronousMethod(async, fail, val) except Exception, e: - print "%s:\n%s" % (e.__class__, e) - self.assert_(fail) + self.assert_(fail, '%s: %s' % (e.__class__, e)) + print "Expected failure: %s: %s" % (e.__class__, e) else: - self.assert_(not fail) - print val, ret - self.assert_(val == ret) + self.assert_(not fail, 'Expected failure but succeeded?!') + self.assertEquals(val, ret.object) def testBusInstanceCaching(self): print "\n********* Testing dbus.Bus instance sharing *********" @@ -262,7 +267,7 @@ class TestDBusBindings(unittest.TestCase): del names bus = dbus.Bus() - ret = _dbus_bindings.bus_name_has_owner(bus._connection, 'org.freedesktop.DBus.Python.TestName') + ret = bus.name_has_owner('org.freedesktop.DBus.Python.TestName') self.assert_(not ret, 'deleting reference failed to release BusName org.freedesktop.DBus.Python.TestName') """ Remove this for now @@ -298,7 +303,7 @@ class TestDBusPythonToGLibBindings(unittest.TestCase): for send_val in test_types_vals: print "Testing %s"% str(send_val) recv_val = self.iface.EchoVariant(send_val) - self.assertEquals(send_val, recv_val) + self.assertEquals(send_val, recv_val.object) """ if __name__ == '__main__': gobject.threads_init() diff --git a/test/test-service.py b/test/test-service.py index 975ea79..bd985eb 100755 --- a/test/test-service.py +++ b/test/test-service.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import sys import os +import logging builddir = os.environ["DBUS_TOP_BUILDDIR"] pydir = builddir @@ -11,7 +12,6 @@ sys.path.insert(0, pydir + 'dbus') import dbus if not dbus.__file__.startswith(pydir): - os.system("echo %s> /tmp/dbus.log"%pydir) raise Exception("DBus modules are not being picked up from the package") import dbus.service @@ -19,6 +19,12 @@ import dbus.glib import gobject import random + +logging.basicConfig(filename=pydir + '/test-service.log', filemode='w') +logging.getLogger().setLevel(1) +logger = logging.getLogger('test-service') + + class TestInterface(dbus.service.Interface): @dbus.service.method("org.freedesktop.DBus.TestSuiteInterface", in_signature='', out_signature='b') def CheckInheritance(self): @@ -68,6 +74,7 @@ class TestObject(dbus.service.Object, TestInterface): @dbus.service.method("org.freedesktop.DBus.TestSuiteInterface", in_signature='u', out_signature='(ss)') def ReturnStruct(self, test): + logger.info('ReturnStruct(%r) -> %r', test, self.returnValue(test)) return self.returnValue(test) @dbus.service.method("org.freedesktop.DBus.TestSuiteInterface", in_signature='u', out_signature='as') @@ -80,15 +87,15 @@ class TestObject(dbus.service.Object, TestInterface): @dbus.service.signal("org.freedesktop.DBus.TestSuiteInterface", signature='s') def SignalOneString(self, test): - pass + logger.info('SignalOneString(%r)', test) @dbus.service.signal("org.freedesktop.DBus.TestSuiteInterface", signature='ss') def SignalTwoStrings(self, test, test2): - pass + logger.info('SignalTwoStrings(%r, %r)', test, test2) @dbus.service.signal("org.freedesktop.DBus.TestSuiteInterface", signature='(ss)') def SignalStruct(self, test): - pass + logger.info('SignalStruct(%r)', test) @dbus.service.signal("org.freedesktop.DBus.TestSuiteInterface", signature='as') def SignalArray(self, test): @@ -110,6 +117,7 @@ class TestObject(dbus.service.Object, TestInterface): else: val = tuple([val]) + logger.info('Emitting %s with %r', signal, val) sig(*val) def CheckInheritance(self): diff --git a/test/test-standalone.py b/test/test-standalone.py new file mode 100755 index 0000000..454c6aa --- /dev/null +++ b/test/test-standalone.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python +import sys +import os +import unittest +import time + +builddir = os.environ["DBUS_TOP_BUILDDIR"] +pydir = builddir + +sys.path.insert(0, pydir) +sys.path.insert(0, pydir + 'dbus') + +import _dbus_bindings +import dbus +import dbus.types as types + +pkg = dbus.__file__ +if not pkg.startswith(pydir): + raise Exception("DBus modules (%s) are not being picked up from the package"%pkg) + +if not _dbus_bindings.__file__.startswith(pydir): + raise Exception("DBus modules (%s) are not being picked up from the package"%_dbus_bindings.__file__) + +class TestTypes(unittest.TestCase): + + def test_append(self): + aeq = self.assertEquals + from _dbus_bindings import SignalMessage + s = SignalMessage('/', 'foo.bar', 'baz') + s.append([types.Byte(1)], signature='ay') + aeq(s.get_signature(), 'ay') + aeq(s.get_args_list(), [[types.Byte(1)]]) + + s = SignalMessage('/', 'foo.bar', 'baz') + s.append([], signature='ay') + aeq(s.get_args_list(), [[]]) + + def test_append_ByteArray(self): + aeq = self.assertEquals + from _dbus_bindings import SignalMessage + s = SignalMessage('/', 'foo.bar', 'baz') + s.append(types.ByteArray('ab'), signature='ay') + aeq(s.get_args_list(), [[types.Byte('a'), types.Byte('b')]]) + s = SignalMessage('/', 'foo.bar', 'baz') + s.append(types.ByteArray('ab'), signature='av') + aeq(s.get_args_list(), [[types.Variant(types.Byte('a')), + types.Variant(types.Byte('b'))]]) + + def test_append_Variant(self): + a = self.assert_ + aeq = self.assertEquals + from _dbus_bindings import SignalMessage + s = SignalMessage('/', 'foo.bar', 'baz') + s.append(types.Variant(1, signature='i'), + types.Variant('a', signature='s'), + types.Variant([(types.Variant('a', signature='y'), 'b'), + (types.Variant(123, signature='u'), 1)], + signature='a(vy)')) + aeq(s.get_signature(), 'vvv') + args = s.get_args_list() + aeq(args[0].__class__, types.Variant) + aeq(args[0].signature, 'i') + aeq(args[0].object.__class__, types.Int32) + aeq(args[0].object, 1) + aeq(args[1].__class__, types.Variant) + aeq(args[1].signature, 's') + a(isinstance(args[1].object, unicode)) + aeq(args[2].__class__, types.Variant) + aeq(args[1].object, 'a') + aeq(args[2].signature, 'a(vy)') + avy = args[2].object + aeq(avy.__class__, types.Array) + aeq(len(avy), 2) + aeq(avy[0].__class__, tuple) + aeq(len(avy[0]), 2) + aeq(avy[0][0].__class__, types.Variant) + aeq(avy[0][0].signature, 'y') + aeq(avy[0][0].object.__class__, types.Byte) + aeq(avy[0][0].object, types.Byte('a')) + aeq(avy[0][1].__class__, types.Byte) + aeq(avy[0][1], types.Byte('b')) + aeq(avy[1].__class__, tuple) + aeq(len(avy[1]), 2) + aeq(avy[1][0].__class__, types.Variant) + aeq(avy[1][0].signature, 'u') + aeq(avy[1][0].object.__class__, types.UInt32) + aeq(avy[1][0].object, 123) + aeq(avy[1][1].__class__, types.Byte) + aeq(avy[1][1], types.Byte(1)) + + def test_Variant(self): + Variant = types.Variant + a = self.assert_ + a(Variant(1, 'i') == Variant(1, 'i')) + a(not (Variant(1, 'i') == Variant(1, 'u'))) + a(not (Variant(1, 'i') == Variant(2, 'i'))) + a(not (Variant(1, 'i') == Variant(2, 'u'))) + a(not (Variant(1, 'i') != Variant(1, 'i'))) + a(Variant(1, 'i') != Variant(1, 'u')) + a(Variant(1, 'i') != Variant(2, 'i')) + a(Variant(1, 'i') != Variant(2, 'u')) + + def test_Signature(self): + self.assertRaises(Exception, types.Signature, 'a') + self.assertEquals(types.Signature('ab'), 'ab') + self.assert_(isinstance(types.Signature('ab'), str)) + self.assertEquals(tuple(types.Signature('ab(xt)a{sv}')), + ('ab', '(xt)', 'a{sv}')) + self.assert_(isinstance(tuple(types.Signature('ab'))[0], + types.Signature)) + + def test_guess_signature(self): + aeq = self.assertEquals + from _dbus_bindings import Message + aeq(Message.guess_signature(('a','b')), '(ss)') + aeq(Message.guess_signature('a','b'), 'ss') + aeq(Message.guess_signature(['a','b']), 'as') + aeq(Message.guess_signature(('a',)), '(s)') + aeq(Message.guess_signature('abc'), 's') + aeq(Message.guess_signature(types.Int32(123)), 'i') + aeq(Message.guess_signature(('a',)), '(s)') + aeq(Message.guess_signature(['a']), 'as') + aeq(Message.guess_signature({'a':'b'}), 'a{ss}') + +if __name__ == '__main__': + unittest.main() -- 2.11.4.GIT