More work on D-Bus error messages

* lisp/net/dbus.el (dbus-get-property): Adapt docstring.
(dbus-set-property): Handle case of `:write' access type.
(dbus-get-other-registered-properties): Rename from
`dbus-get-other-registered-property'.
(dbus-property-handler): Fix thinkos.

* src/dbusbind.c (xd_read_message_1): Add error_name to event args
in case of DBUS_MESSAGE_TYPE_ERROR.

* test/lisp/net/dbus-tests.el (dbus--test-enabled-session-bus)
(dbus--test-enabled-system-bus): Make them defconst.
(dbus--test-service, dbus--test-path, dbus--test-interface):
New defconst.  Replace all occurences of `dbus-service-emacs' by
`dbus--test-service'.
(dbus--test-method-handler): New defun.
(dbus-test04-register-method, dbus-test05-register-property): New tests.
This commit is contained in:
Michael Albinus 2020-09-06 20:45:29 +02:00
parent 3444f397c7
commit 9ba575aeb3
3 changed files with 237 additions and 42 deletions

View file

@ -565,8 +565,9 @@ placed in the queue.
`:already-owner': Service is already the primary owner."
;; Add Peer handler.
(dbus-register-method bus service nil dbus-interface-peer "Ping"
#'dbus-peer-handler 'dont-register)
(dbus-register-method
bus service nil dbus-interface-peer "Ping"
#'dbus-peer-handler 'dont-register)
;; Add ObjectManager handler.
(dbus-register-method
@ -1423,7 +1424,7 @@ be \"out\"."
(defun dbus-get-property (bus service path interface property)
"Return the value of PROPERTY of INTERFACE.
It will be checked at BUS, SERVICE, PATH. The result can be any
valid D-Bus value, or nil if there is no PROPERTY."
valid D-Bus value, or nil if there is no PROPERTY, or PROPERTY cannot be read."
(dbus-ignore-errors
;; "Get" returns a variant, so we must use the `car'.
(car
@ -1440,8 +1441,11 @@ successfully set return VALUE. Otherwise, return nil."
(dbus-call-method
bus service path dbus-interface-properties
"Set" :timeout 500 interface property (list :variant value))
;; Return VALUE.
(dbus-get-property bus service path interface property)))
;; Return VALUE. The property could have the `:write' access type,
;; so we ignore errors in `dbus-get-property'.
(or
(dbus-ignore-errors (dbus-get-property bus service path interface property))
value)))
(defun dbus-get-all-properties (bus service path interface)
"Return all properties of INTERFACE at BUS, SERVICE, PATH.
@ -1465,7 +1469,8 @@ Filter out not matching PATH."
(gethash (list :property bus interface property)
dbus-registered-objects-table)))
(defun dbus-get-other-registered-property (bus _service path interface property)
(defun dbus-get-other-registered-properties
(bus _service path interface property)
"Return PROPERTY entry of `dbus-registered-objects-table'.
Filter out matching PATH."
;; Remove matching entries.
@ -1551,7 +1556,7 @@ clients from discovering the still incomplete interface."
(cons
(if emits-signal (list access :emits-signal) (list access))
value))
(dbus-get-other-registered-property
(dbus-get-other-registered-properties
bus service path interface property))))
(puthash key val dbus-registered-objects-table)
@ -1578,7 +1583,7 @@ It will be registered for all objects created by `dbus-register-property'."
`(:error ,dbus-error-invalid-args
,(format-message
"No such property \"%s\" at path \"%s\"" property path)))
((eq (car object) :write)
((memq :write (car object))
`(:error ,dbus-error-access-denied
,(format-message
"Property \"%s\" at path \"%s\" is not readable" property path)))
@ -1596,14 +1601,14 @@ It will be registered for all objects created by `dbus-register-property'."
`(:error ,dbus-error-invalid-args
,(format-message
"No such property \"%s\" at path \"%s\"" property path)))
((eq (car object) :read)
((memq :read (car object))
`(:error ,dbus-error-property-read-only
,(format-message
"Property \"%s\" at path \"%s\" is not writable" property path)))
(t (puthash (list :property bus interface property)
(cons (append (butlast (car entry))
(list (cons (car object) value)))
(dbus-get-other-registered-property
(dbus-get-other-registered-properties
bus service path interface property))
dbus-registered-objects-table)
;; Send the "PropertiesChanged" signal.
@ -1625,15 +1630,17 @@ It will be registered for all objects created by `dbus-register-property'."
(let (result)
(maphash
(lambda (key val)
(dolist (item val)
(when (and (equal (butlast key) (list :property bus interface))
(string-equal path (nth 2 item))
(not (functionp (car (last item)))))
(push
(list :dict-entry
(car (last key))
(list :variant (cdar (last item))))
result))))
(when (consp val)
(dolist (item val)
(when (and (equal (butlast key) (list :property bus interface))
(string-equal path (nth 2 item))
(consp (car (last item)))
(not (memq :write (caar (last item)))))
(push
(list :dict-entry
(car (last key))
(list :variant (cdar (last item))))
result)))))
dbus-registered-objects-table)
;; Return the result, or an empty array.
(list :array (or result '(:signature "{sv}"))))))))

View file

@ -1508,7 +1508,7 @@ xd_read_message_1 (DBusConnection *connection, Lisp_Object bus)
int mtype;
dbus_uint32_t serial;
unsigned int ui_serial;
const char *uname, *path, *interface, *member;
const char *uname, *path, *interface, *member, *error_name;
dmessage = dbus_connection_pop_message (connection);
@ -1544,10 +1544,11 @@ xd_read_message_1 (DBusConnection *connection, Lisp_Object bus)
path = dbus_message_get_path (dmessage);
interface = dbus_message_get_interface (dmessage);
member = dbus_message_get_member (dmessage);
error_name =dbus_message_get_error_name (dmessage);
XD_DEBUG_MESSAGE ("Event received: %s %u %s %s %s %s %s",
XD_DEBUG_MESSAGE ("Event received: %s %u %s %s %s %s %s %s",
XD_MESSAGE_TYPE_TO_STRING (mtype),
ui_serial, uname, path, interface, member,
ui_serial, uname, path, interface, member, error_name,
XD_OBJECT_TO_STRING (args));
if (mtype == DBUS_MESSAGE_TYPE_INVALID)
@ -1571,7 +1572,10 @@ xd_read_message_1 (DBusConnection *connection, Lisp_Object bus)
EVENT_INIT (event);
event.kind = DBUS_EVENT;
event.frame_or_window = Qnil;
event.arg = Fcons (value, args);
event.arg =
Fcons (value,
(mtype == DBUS_MESSAGE_TYPE_ERROR)
? (Fcons (build_string (error_name), args)) : args);
}
else /* DBUS_MESSAGE_TYPE_METHOD_CALL, DBUS_MESSAGE_TYPE_SIGNAL. */

View file

@ -25,16 +25,25 @@
(defvar dbus-debug nil)
(declare-function dbus-get-unique-name "dbusbind.c" (bus))
(defvar dbus--test-enabled-session-bus
(defconst dbus--test-enabled-session-bus
(and (featurep 'dbusbind)
(dbus-ignore-errors (dbus-get-unique-name :session)))
"Check, whether we are registered at the session bus.")
(defvar dbus--test-enabled-system-bus
(defconst dbus--test-enabled-system-bus
(and (featurep 'dbusbind)
(dbus-ignore-errors (dbus-get-unique-name :system)))
"Check, whether we are registered at the system bus.")
(defconst dbus--test-service "org.gnu.Emacs.TestDBus"
"Test service.")
(defconst dbus--test-path "/org/gnu/Emacs/TestDBus"
"Test object path.")
(defconst dbus--test-interface "org.gnu.Emacs.TestDBus"
"Test interface.")
(defun dbus--test-availability (bus)
"Test availability of D-Bus BUS."
(should (dbus-list-names bus))
@ -85,19 +94,19 @@
(defun dbus--test-register-service (bus)
"Check service registration at BUS."
;; Cleanup.
(dbus-ignore-errors (dbus-unregister-service bus dbus-service-emacs))
(dbus-ignore-errors (dbus-unregister-service bus dbus--test-service))
;; Register an own service.
(should (eq (dbus-register-service bus dbus-service-emacs) :primary-owner))
(should (member dbus-service-emacs (dbus-list-known-names bus)))
(should (eq (dbus-register-service bus dbus-service-emacs) :already-owner))
(should (member dbus-service-emacs (dbus-list-known-names bus)))
(should (eq (dbus-register-service bus dbus--test-service) :primary-owner))
(should (member dbus--test-service (dbus-list-known-names bus)))
(should (eq (dbus-register-service bus dbus--test-service) :already-owner))
(should (member dbus--test-service (dbus-list-known-names bus)))
;; Unregister the service.
(should (eq (dbus-unregister-service bus dbus-service-emacs) :released))
(should-not (member dbus-service-emacs (dbus-list-known-names bus)))
(should (eq (dbus-unregister-service bus dbus-service-emacs) :non-existent))
(should-not (member dbus-service-emacs (dbus-list-known-names bus)))
(should (eq (dbus-unregister-service bus dbus--test-service) :released))
(should-not (member dbus--test-service (dbus-list-known-names bus)))
(should (eq (dbus-unregister-service bus dbus--test-service) :non-existent))
(should-not (member dbus--test-service (dbus-list-known-names bus)))
;; `dbus-service-dbus' is reserved for the BUS itself.
(should-error (dbus-register-service bus dbus-service-dbus))
@ -106,7 +115,7 @@
(ert-deftest dbus-test02-register-service-session ()
"Check service registration at `:session' bus."
(skip-unless (and dbus--test-enabled-session-bus
(dbus-register-service :session dbus-service-emacs)))
(dbus-register-service :session dbus--test-service)))
(dbus--test-register-service :session)
(let ((service "org.freedesktop.Notifications"))
@ -124,7 +133,7 @@
(ert-deftest dbus-test02-register-service-system ()
"Check service registration at `:system' bus."
(skip-unless (and dbus--test-enabled-system-bus
(dbus-register-service :system dbus-service-emacs)))
(dbus-register-service :system dbus--test-service)))
(dbus--test-register-service :system))
(ert-deftest dbus-test02-register-service-own-bus ()
@ -148,7 +157,7 @@ This includes initialization and closing the bus."
(featurep 'dbusbind)
(dbus-init-bus bus)
(dbus-get-unique-name bus)
(dbus-register-service bus dbus-service-emacs))))
(dbus-register-service bus dbus--test-service))))
;; Run the test.
(dbus--test-register-service bus))
@ -159,19 +168,194 @@ This includes initialization and closing the bus."
"Check `dbus-interface-peer' methods."
(skip-unless
(and dbus--test-enabled-session-bus
(dbus-register-service :session dbus-service-emacs)
(dbus-register-service :session dbus--test-service)
;; "GetMachineId" is not implemented (yet). When it returns a
;; value, another D-Bus client like dbus-monitor is reacting
;; on `dbus-interface-peer'. We cannot test then.
(not
(dbus-ignore-errors
(dbus-call-method
:session dbus-service-emacs dbus-path-dbus
:session dbus--test-service dbus-path-dbus
dbus-interface-peer "GetMachineId" :timeout 100)))))
(should (dbus-ping :session dbus-service-emacs 100))
(dbus-unregister-service :session dbus-service-emacs)
(should-not (dbus-ping :session dbus-service-emacs 100)))
(should (dbus-ping :session dbus--test-service 100))
(dbus-unregister-service :session dbus--test-service)
(should-not (dbus-ping :session dbus--test-service 100)))
(defun dbus--test-method-handler (&rest args)
"Method handler for `dbus-test04-register-method'."
(cond
;; No argument.
((null args)
:ignore)
;; One argument.
((= 1 (length args))
(car args))
;; Two arguments.
((= 2 (length args))
`(:error ,dbus-error-invalid-args
,(format-message "Wrong arguments %s" args)))
;; More than two arguments.
(t (signal 'dbus-error (cons "D-Bus signal" args)))))
(ert-deftest dbus-test04-register-method ()
"Check method registration for an own service."
(skip-unless dbus--test-enabled-session-bus)
(dbus-ignore-errors (dbus-unregister-service :session dbus--test-service))
(unwind-protect
(let ((method "Method")
(handler #'dbus--test-method-handler))
(should
(equal
(dbus-register-method
:session dbus--test-service dbus--test-path
dbus--test-interface method handler)
`((:method :session ,dbus--test-interface ,method)
(,dbus--test-service ,dbus--test-path ,handler))))
;; No argument, returns nil.
(should-not
(dbus-call-method
:session dbus--test-service dbus--test-path
dbus--test-interface method))
;; One argument, returns the argument.
(should
(string-equal
(dbus-call-method
:session dbus--test-service dbus--test-path
dbus--test-interface method "foo")
"foo"))
;; Two arguments, D-Bus error activated as `(:error ...)' list.
(should
(equal
(should-error
(dbus-call-method
:session dbus--test-service dbus--test-path
dbus--test-interface method "foo" "bar"))
`(dbus-error ,dbus-error-invalid-args "Wrong arguments (foo bar)")))
;; Three arguments, D-Bus error activated by `dbus-error' signal.
(should
(equal
(should-error
(dbus-call-method
:session dbus--test-service dbus--test-path
dbus--test-interface method "foo" "bar" "baz"))
`(dbus-error
,dbus-error-failed
"D-Bus error: \"D-Bus signal\", \"foo\", \"bar\", \"baz\""))))
;; Cleanup.
(dbus-unregister-service :session dbus--test-service)))
(ert-deftest dbus-test05-register-property ()
"Check property registration for an own service."
(skip-unless dbus--test-enabled-session-bus)
(dbus-ignore-errors (dbus-unregister-service :session dbus--test-service))
(unwind-protect
(let ((property1 "Property1")
(property2 "Property2")
(property3 "Property3"))
;; `:read' property.
(should
(equal
(dbus-register-property
:session dbus--test-service dbus--test-path
dbus--test-interface property1 :read "foo")
`((:property :session "org.gnu.Emacs.TestDBus" ,property1)
(,dbus--test-service ,dbus--test-path))))
(should
(string-equal
(dbus-get-property
:session dbus--test-service dbus--test-path
dbus--test-interface property1)
"foo"))
(should-not ;; Due to `:read' access type.
(dbus-set-property
:session dbus--test-service dbus--test-path
dbus--test-interface property1 "foofoo"))
(should
(string-equal
(dbus-get-property
:session dbus--test-service dbus--test-path
dbus--test-interface property1)
"foo"))
;; `:write' property.
(should
(equal
(dbus-register-property
:session dbus--test-service dbus--test-path
dbus--test-interface property2 :write "bar")
`((:property :session "org.gnu.Emacs.TestDBus" ,property2)
(,dbus--test-service ,dbus--test-path))))
(should-not ;; Due to `:write' access type.
(dbus-get-property
:session dbus--test-service dbus--test-path
dbus--test-interface property2))
(should
(string-equal
(dbus-set-property
:session dbus--test-service dbus--test-path
dbus--test-interface property2 "barbar")
"barbar"))
(should-not ;; Due to `:write' access type.
(dbus-get-property
:session dbus--test-service dbus--test-path
dbus--test-interface property2))
;; `:readwrite' property.
(should
(equal
(dbus-register-property
:session dbus--test-service dbus--test-path
dbus--test-interface property3 :readwrite "baz")
`((:property :session "org.gnu.Emacs.TestDBus" ,property3)
(,dbus--test-service ,dbus--test-path))))
(should
(string-equal
(dbus-get-property
:session dbus--test-service dbus--test-path
dbus--test-interface property3)
"baz"))
(should
(string-equal
(dbus-set-property
:session dbus--test-service dbus--test-path
dbus--test-interface property3 "bazbaz")
"bazbaz"))
(should
(string-equal
(dbus-get-property
:session dbus--test-service dbus--test-path
dbus--test-interface property3)
"bazbaz"))
;; `dbus-get-all-properties'. We cannot retrieve a value for
;; the property with `:write' access type.
(let ((result
(dbus-get-all-properties
:session dbus--test-service dbus--test-path
dbus--test-interface)))
(should (string-equal (cdr (assoc property1 result)) "foo"))
(should (string-equal (cdr (assoc property3 result)) "bazbaz"))
(should-not (assoc property2 result))))
;; FIXME: This is wrong! The properties are missing.
;; (should
;; (equal
;; (dbus-get-all-managed-objects
;; :session dbus--test-service dbus--test-path)
;; `((,dbus--test-path
;; ((,dbus-interface-peer)
;; (,dbus-interface-objectmanager)
;; (,dbus-interface-properties)))))))
;; Cleanup.
(dbus-unregister-service :session dbus--test-service)))
(defun dbus-test-all (&optional interactive)
"Run all tests for \\[dbus]."