Home | History | Annotate | Download | only in dbus
      1 // Copyright 2014 The Chromium OS Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // Helper utilities to simplify testing of D-Bus object implementations.
      6 // Since the method handlers could now be asynchronous, they use callbacks to
      7 // provide method return values. This makes it really difficult to invoke
      8 // such handlers in unit tests (even if they are actually synchronous but
      9 // still use DBusMethodResponse to send back the method results).
     10 // This file provide testing-only helpers to make calling D-Bus method handlers
     11 // easier.
     12 #ifndef LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_
     13 #define LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_
     14 
     15 #include <base/bind.h>
     16 #include <base/memory/weak_ptr.h>
     17 #include <brillo/dbus/dbus_method_invoker.h>
     18 #include <brillo/dbus/dbus_object.h>
     19 
     20 namespace brillo {
     21 namespace dbus_utils {
     22 
     23 // Helper friend class to call DBusInterface::HandleMethodCall() since it is
     24 // a private method of the class and we don't want to make it public.
     25 class DBusInterfaceTestHelper final {
     26  public:
     27   static void HandleMethodCall(DBusInterface* itf,
     28                                dbus::MethodCall* method_call,
     29                                ResponseSender sender) {
     30     itf->HandleMethodCall(method_call, sender);
     31   }
     32 };
     33 
     34 namespace testing {
     35 
     36 // This is a simple class that has weak pointer semantics and holds an
     37 // instance of D-Bus method call response message. We use this in tests
     38 // to get the response in case the handler processes a method call request
     39 // synchronously. Otherwise the ResponseHolder object will be destroyed and
     40 // ResponseHolder::ReceiveResponse() will not be called since we bind the
     41 // callback to the object instance via a weak pointer.
     42 struct ResponseHolder final : public base::SupportsWeakPtr<ResponseHolder> {
     43   void ReceiveResponse(std::unique_ptr<dbus::Response> response) {
     44     response_ = std::move(response);
     45   }
     46 
     47   std::unique_ptr<dbus::Response> response_;
     48 };
     49 
     50 // Dispatches a D-Bus method call to the corresponding handler.
     51 // Used mostly for testing purposes. This method is inlined so that it is
     52 // not included in the shipping code of libbrillo, and included at the
     53 // call sites. Returns a response from the method handler or nullptr if the
     54 // method hasn't provided the response message immediately
     55 // (i.e. it is asynchronous).
     56 inline std::unique_ptr<dbus::Response> CallMethod(
     57     const DBusObject& object, dbus::MethodCall* method_call) {
     58   DBusInterface* itf = object.FindInterface(method_call->GetInterface());
     59   std::unique_ptr<dbus::Response> response;
     60   if (!itf) {
     61     response = CreateDBusErrorResponse(
     62         method_call,
     63         DBUS_ERROR_UNKNOWN_INTERFACE,
     64         "Interface you invoked a method on isn't known by the object.");
     65   } else {
     66     ResponseHolder response_holder;
     67     DBusInterfaceTestHelper::HandleMethodCall(
     68       itf, method_call, base::Bind(&ResponseHolder::ReceiveResponse,
     69                                    response_holder.AsWeakPtr()));
     70     response = std::move(response_holder.response_);
     71   }
     72   return response;
     73 }
     74 
     75 // MethodHandlerInvoker is similar to CallMethod() function above, except
     76 // it allows the callers to invoke the method handlers directly bypassing
     77 // the DBusObject/DBusInterface infrastructure.
     78 // This works only on synchronous methods though. The handler must reply
     79 // before the handler exits.
     80 template<typename RetType>
     81 struct MethodHandlerInvoker {
     82   // MethodHandlerInvoker<RetType>::Call() calls a member |method| of a class
     83   // |instance| and passes the |args| to it. The method's return value provided
     84   // via handler's DBusMethodResponse is then extracted and returned.
     85   // If the method handler returns an error, the error information is passed
     86   // to the caller via the |error| object (and the method returns a default
     87   // value of type RetType as a placeholder).
     88   // If the method handler asynchronous and did not provide a reply (success or
     89   // error) before the handler exits, this method aborts with a CHECK().
     90   template<class Class, typename... Params, typename... Args>
     91   static RetType Call(
     92       ErrorPtr* error,
     93       Class* instance,
     94       void(Class::*method)(std::unique_ptr<DBusMethodResponse<RetType>>,
     95                            Params...),
     96       Args... args) {
     97     ResponseHolder response_holder;
     98     dbus::MethodCall method_call("test.interface", "TestMethod");
     99     method_call.SetSerial(123);
    100     std::unique_ptr<DBusMethodResponse<RetType>> method_response{
    101       new DBusMethodResponse<RetType>(
    102         &method_call, base::Bind(&ResponseHolder::ReceiveResponse,
    103                                  response_holder.AsWeakPtr()))
    104     };
    105     (instance->*method)(std::move(method_response), args...);
    106     CHECK(response_holder.response_.get())
    107         << "No response received. Asynchronous methods are not supported.";
    108     RetType ret_val;
    109     ExtractMethodCallResults(response_holder.response_.get(), error, &ret_val);
    110     return ret_val;
    111   }
    112 };
    113 
    114 // Specialization of MethodHandlerInvoker for methods that do not return
    115 // values (void methods).
    116 template<>
    117 struct MethodHandlerInvoker<void> {
    118   template<class Class, typename... Params, typename... Args>
    119   static void Call(
    120       ErrorPtr* error,
    121       Class* instance,
    122       void(Class::*method)(std::unique_ptr<DBusMethodResponse<>>, Params...),
    123       Args... args) {
    124     ResponseHolder response_holder;
    125     dbus::MethodCall method_call("test.interface", "TestMethod");
    126     method_call.SetSerial(123);
    127     std::unique_ptr<DBusMethodResponse<>> method_response{
    128       new DBusMethodResponse<>(&method_call,
    129                                base::Bind(&ResponseHolder::ReceiveResponse,
    130                                           response_holder.AsWeakPtr()))
    131     };
    132     (instance->*method)(std::move(method_response), args...);
    133     CHECK(response_holder.response_.get())
    134         << "No response received. Asynchronous methods are not supported.";
    135     ExtractMethodCallResults(response_holder.response_.get(), error);
    136   }
    137 };
    138 
    139 }  // namespace testing
    140 }  // namespace dbus_utils
    141 }  // namespace brillo
    142 
    143 #endif  // LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_
    144