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:
HandleMethodCall(DBusInterface * itf,dbus::MethodCall * method_call,ResponseSender sender)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> {
ReceiveResponsefinal43   void ReceiveResponse(scoped_ptr<dbus::Response> response) {
44     response_.reset(response.release());
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).
CallMethod(const DBusObject & object,dbus::MethodCall * method_call)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>
CallMethodHandlerInvoker91   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