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