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