// Copyright 2014 The Chromium OS Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Helper utilities to simplify testing of D-Bus object implementations. // Since the method handlers could now be asynchronous, they use callbacks to // provide method return values. This makes it really difficult to invoke // such handlers in unit tests (even if they are actually synchronous but // still use DBusMethodResponse to send back the method results). // This file provide testing-only helpers to make calling D-Bus method handlers // easier. #ifndef LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_ #define LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_ #include <base/bind.h> #include <base/memory/weak_ptr.h> #include <brillo/dbus/dbus_method_invoker.h> #include <brillo/dbus/dbus_object.h> namespace brillo { namespace dbus_utils { // Helper friend class to call DBusInterface::HandleMethodCall() since it is // a private method of the class and we don't want to make it public. class DBusInterfaceTestHelper final { public: static void HandleMethodCall(DBusInterface* itf, dbus::MethodCall* method_call, ResponseSender sender) { itf->HandleMethodCall(method_call, sender); } }; namespace testing { // This is a simple class that has weak pointer semantics and holds an // instance of D-Bus method call response message. We use this in tests // to get the response in case the handler processes a method call request // synchronously. Otherwise the ResponseHolder object will be destroyed and // ResponseHolder::ReceiveResponse() will not be called since we bind the // callback to the object instance via a weak pointer. struct ResponseHolder final : public base::SupportsWeakPtr<ResponseHolder> { void ReceiveResponse(std::unique_ptr<dbus::Response> response) { response_ = std::move(response); } std::unique_ptr<dbus::Response> response_; }; // Dispatches a D-Bus method call to the corresponding handler. // Used mostly for testing purposes. This method is inlined so that it is // not included in the shipping code of libbrillo, and included at the // call sites. Returns a response from the method handler or nullptr if the // method hasn't provided the response message immediately // (i.e. it is asynchronous). inline std::unique_ptr<dbus::Response> CallMethod( const DBusObject& object, dbus::MethodCall* method_call) { DBusInterface* itf = object.FindInterface(method_call->GetInterface()); std::unique_ptr<dbus::Response> response; if (!itf) { response = CreateDBusErrorResponse( method_call, DBUS_ERROR_UNKNOWN_INTERFACE, "Interface you invoked a method on isn't known by the object."); } else { ResponseHolder response_holder; DBusInterfaceTestHelper::HandleMethodCall( itf, method_call, base::Bind(&ResponseHolder::ReceiveResponse, response_holder.AsWeakPtr())); response = std::move(response_holder.response_); } return response; } // MethodHandlerInvoker is similar to CallMethod() function above, except // it allows the callers to invoke the method handlers directly bypassing // the DBusObject/DBusInterface infrastructure. // This works only on synchronous methods though. The handler must reply // before the handler exits. template<typename RetType> struct MethodHandlerInvoker { // MethodHandlerInvoker<RetType>::Call() calls a member |method| of a class // |instance| and passes the |args| to it. The method's return value provided // via handler's DBusMethodResponse is then extracted and returned. // If the method handler returns an error, the error information is passed // to the caller via the |error| object (and the method returns a default // value of type RetType as a placeholder). // If the method handler asynchronous and did not provide a reply (success or // error) before the handler exits, this method aborts with a CHECK(). template<class Class, typename... Params, typename... Args> static RetType Call( ErrorPtr* error, Class* instance, void(Class::*method)(std::unique_ptr<DBusMethodResponse<RetType>>, Params...), Args... args) { ResponseHolder response_holder; dbus::MethodCall method_call("test.interface", "TestMethod"); method_call.SetSerial(123); std::unique_ptr<DBusMethodResponse<RetType>> method_response{ new DBusMethodResponse<RetType>( &method_call, base::Bind(&ResponseHolder::ReceiveResponse, response_holder.AsWeakPtr())) }; (instance->*method)(std::move(method_response), args...); CHECK(response_holder.response_.get()) << "No response received. Asynchronous methods are not supported."; RetType ret_val; ExtractMethodCallResults(response_holder.response_.get(), error, &ret_val); return ret_val; } }; // Specialization of MethodHandlerInvoker for methods that do not return // values (void methods). template<> struct MethodHandlerInvoker<void> { template<class Class, typename... Params, typename... Args> static void Call( ErrorPtr* error, Class* instance, void(Class::*method)(std::unique_ptr<DBusMethodResponse<>>, Params...), Args... args) { ResponseHolder response_holder; dbus::MethodCall method_call("test.interface", "TestMethod"); method_call.SetSerial(123); std::unique_ptr<DBusMethodResponse<>> method_response{ new DBusMethodResponse<>(&method_call, base::Bind(&ResponseHolder::ReceiveResponse, response_holder.AsWeakPtr())) }; (instance->*method)(std::move(method_response), args...); CHECK(response_holder.response_.get()) << "No response received. Asynchronous methods are not supported."; ExtractMethodCallResults(response_holder.response_.get(), error); } }; } // namespace testing } // namespace dbus_utils } // namespace brillo #endif // LIBBRILLO_BRILLO_DBUS_DBUS_OBJECT_TEST_HELPERS_H_