xref: /aosp_15_r20/external/libbrillo/brillo/dbus/dbus_object_test_helpers.h (revision 1a96fba65179ea7d3f56207137718607415c5953)
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