// 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_