blob: 9cad910b7d30ff3b237b478093ee12bc7934c3c3 [file] [log] [blame]
// Copyright 2017 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.
#include "trunks/trunks_dbus_proxy.h"
#include <memory>
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/threading/thread.h>
#include <dbus/object_proxy.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "trunks/dbus_interface.h"
#include "trunks/error_codes.h"
#include "trunks/interface.pb.h"
#include "trunks/mock_dbus_bus.h"
using testing::_;
using testing::NiceMock;
using testing::Return;
namespace {
class FakeObjectProxy : public dbus::ObjectProxy {
public:
FakeObjectProxy()
: dbus::ObjectProxy(
nullptr, "", dbus::ObjectPath(trunks::kTrunksServicePath), 0) {}
void CallMethodWithErrorCallback(
dbus::MethodCall* method_call,
int timeout_ms,
dbus::ObjectProxy::ResponseCallback callback,
dbus::ObjectProxy::ErrorCallback error_callback) override {
dbus::ScopedDBusError error;
std::unique_ptr<dbus::Response> response =
CallMethodAndBlockWithErrorDetails(method_call, timeout_ms, &error);
if (response) {
std::move(callback).Run(response.get());
} else {
method_call->SetSerial(1);
std::unique_ptr<dbus::ErrorResponse> error_response =
dbus::ErrorResponse::FromMethodCall(method_call, "org.MyError",
"Error message");
std::move(error_callback).Run(error_response.get());
}
}
std::unique_ptr<dbus::Response> CallMethodAndBlockWithErrorDetails(
dbus::MethodCall* method_call,
int /* timeout_ms */,
dbus::ScopedDBusError* error) override {
dbus::MessageReader reader(method_call);
trunks::SendCommandRequest command_proto;
brillo::dbus_utils::PopValueFromReader(&reader, &command_proto);
last_command_ = command_proto.command();
if (next_response_.empty()) {
return std::unique_ptr<dbus::Response>();
}
std::unique_ptr<dbus::Response> dbus_response =
dbus::Response::CreateEmpty();
dbus::MessageWriter writer(dbus_response.get());
trunks::SendCommandResponse response_proto;
response_proto.set_response(next_response_);
brillo::dbus_utils::AppendValueToWriter(&writer, response_proto);
return dbus_response;
}
std::string next_response_;
std::string last_command_;
};
} // namespace
namespace trunks {
class TrunksDBusProxyTest : public testing::Test {
public:
TrunksDBusProxyTest() {
proxy_.set_init_timeout(base::TimeDelta());
proxy_.set_init_attempt_delay(base::TimeDelta());
}
void SetUp() override {
ON_CALL(*bus_, Connect()).WillByDefault(Return(true));
ON_CALL(*bus_, GetObjectProxy(_, _))
.WillByDefault(Return(object_proxy_.get()));
ON_CALL(*bus_, GetServiceOwnerAndBlock(_, _))
.WillByDefault(Return("test-service-owner"));
}
void set_next_response(const std::string& response) {
object_proxy_->next_response_ = response;
}
std::string last_command() const {
std::string last_command = object_proxy_->last_command_;
object_proxy_->last_command_.clear();
return last_command;
}
protected:
scoped_refptr<FakeObjectProxy> object_proxy_ = new FakeObjectProxy();
scoped_refptr<NiceMock<MockDBusBus>> bus_ = new NiceMock<MockDBusBus>();
TrunksDBusProxy proxy_{bus_.get()};
};
TEST_F(TrunksDBusProxyTest, InitSuccess) {
EXPECT_CALL(*bus_, GetServiceOwnerAndBlock(_, _))
.WillOnce(Return("test-service-owner"))
.WillOnce(Return("test-service-owner"));
// Before initialization IsServiceReady fails without checking.
EXPECT_FALSE(proxy_.IsServiceReady(false /* force_check */));
EXPECT_FALSE(proxy_.IsServiceReady(true /* force_check */));
EXPECT_TRUE(proxy_.Init());
EXPECT_TRUE(proxy_.IsServiceReady(false /* force_check */));
EXPECT_TRUE(proxy_.IsServiceReady(true /* force_check */));
}
TEST_F(TrunksDBusProxyTest, InitFailure) {
EXPECT_CALL(*bus_, GetServiceOwnerAndBlock(_, _)).WillRepeatedly(Return(""));
EXPECT_FALSE(proxy_.Init());
EXPECT_FALSE(proxy_.IsServiceReady(false /* force_check */));
EXPECT_FALSE(proxy_.IsServiceReady(true /* force_check */));
}
TEST_F(TrunksDBusProxyTest, InitRetrySuccess) {
proxy_.set_init_timeout(base::TimeDelta::FromMilliseconds(100));
EXPECT_CALL(*bus_, GetServiceOwnerAndBlock(_, _))
.WillOnce(Return(""))
.WillOnce(Return("test-service-owner"))
.WillOnce(Return("test-service-owner"));
EXPECT_TRUE(proxy_.Init());
EXPECT_TRUE(proxy_.IsServiceReady(false /* force_check */));
EXPECT_TRUE(proxy_.IsServiceReady(true /* force_check */));
}
TEST_F(TrunksDBusProxyTest, SendCommandSuccess) {
EXPECT_TRUE(proxy_.Init());
set_next_response("response");
auto callback = [](const std::string& response) {
EXPECT_EQ("response", response);
};
proxy_.SendCommand("command", base::Bind(callback));
EXPECT_EQ("command", last_command());
}
TEST_F(TrunksDBusProxyTest, SendCommandAndWaitSuccess) {
EXPECT_TRUE(proxy_.Init());
set_next_response("response");
EXPECT_EQ("response", proxy_.SendCommandAndWait("command"));
EXPECT_EQ("command", last_command());
}
TEST_F(TrunksDBusProxyTest, SendCommandFailureInit) {
// If Init() failed, SAPI_RC_NO_CONNECTION should be returned
// without sending a command.
EXPECT_CALL(*bus_, GetServiceOwnerAndBlock(_, _)).WillRepeatedly(Return(""));
EXPECT_FALSE(proxy_.Init());
set_next_response("");
auto callback = [](const std::string& response) {
EXPECT_EQ(CreateErrorResponse(SAPI_RC_NO_CONNECTION), response);
};
proxy_.SendCommand("command", base::Bind(callback));
EXPECT_EQ("", last_command());
}
TEST_F(TrunksDBusProxyTest, SendCommandAndWaitFailureInit) {
// If Init() failed, SAPI_RC_NO_CONNECTION should be returned
// without sending a command.
EXPECT_CALL(*bus_, GetServiceOwnerAndBlock(_, _)).WillRepeatedly(Return(""));
EXPECT_FALSE(proxy_.Init());
set_next_response("");
EXPECT_EQ(CreateErrorResponse(SAPI_RC_NO_CONNECTION),
proxy_.SendCommandAndWait("command"));
EXPECT_EQ("", last_command());
}
TEST_F(TrunksDBusProxyTest, SendCommandFailureNoConnection) {
// If Init() succeeded, but service is later lost, it should return
// SAPI_RC_NO_CONNECTION in case there was no response.
EXPECT_TRUE(proxy_.Init());
EXPECT_CALL(*bus_, GetServiceOwnerAndBlock(_, _)).WillRepeatedly(Return(""));
set_next_response("");
auto callback = [](const std::string& response) {
EXPECT_EQ(CreateErrorResponse(SAPI_RC_NO_CONNECTION), response);
};
proxy_.SendCommand("command", base::Bind(callback));
EXPECT_EQ("command", last_command());
}
TEST_F(TrunksDBusProxyTest, SendCommandAndWaitFailureNoConnection) {
// If Init() succeeded, but service is later lost, it should return
// SAPI_RC_NO_CONNECTION in case there was no response.
EXPECT_TRUE(proxy_.Init());
EXPECT_CALL(*bus_, GetServiceOwnerAndBlock(_, _)).WillRepeatedly(Return(""));
set_next_response("");
EXPECT_EQ(CreateErrorResponse(SAPI_RC_NO_CONNECTION),
proxy_.SendCommandAndWait("command"));
EXPECT_EQ("command", last_command());
}
TEST_F(TrunksDBusProxyTest, SendCommandFailureNoResponse) {
// If Init() succeeded and the service is ready, it should return
// an appropriate error code in case there was no response.
EXPECT_TRUE(proxy_.Init());
set_next_response("");
auto callback = [](const std::string& response) {
EXPECT_EQ(CreateErrorResponse(SAPI_RC_NO_RESPONSE_RECEIVED), response);
};
proxy_.SendCommand("command", base::Bind(callback));
EXPECT_EQ("command", last_command());
}
TEST_F(TrunksDBusProxyTest, SendCommandAndWaitFailureNoResponse) {
// If Init() succeeded and the service is ready, it should return
// an appropriate error code in case there was no response.
EXPECT_TRUE(proxy_.Init());
set_next_response("");
EXPECT_EQ(CreateErrorResponse(SAPI_RC_NO_RESPONSE_RECEIVED),
proxy_.SendCommandAndWait("command"));
EXPECT_EQ("command", last_command());
}
TEST_F(TrunksDBusProxyTest, SendCommandFailureWrongThread) {
// Attempting to send from a wrong thread should return TRUNKS_RC_IPC_ERROR
// without sending the command.
EXPECT_TRUE(proxy_.Init());
// xor 1 would change the thread id without overflow.
base::PlatformThreadId fake_id = proxy_.origin_thread_id_for_testing() ^ 1;
proxy_.set_origin_thread_id_for_testing(fake_id);
set_next_response("response");
auto callback = [](const std::string& response) {
EXPECT_EQ(CreateErrorResponse(TRUNKS_RC_IPC_ERROR), response);
};
proxy_.SendCommand("command", base::Bind(callback));
EXPECT_EQ("", last_command());
}
TEST_F(TrunksDBusProxyTest, SendCommandAndWaitFailureWrongThread) {
// Attempting to send from a wrong thread should return TRUNKS_RC_IPC_ERROR
// without sending the command.
EXPECT_TRUE(proxy_.Init());
// xor 1 would change the thread id without overflow.
base::PlatformThreadId fake_id = proxy_.origin_thread_id_for_testing() ^ 1;
proxy_.set_origin_thread_id_for_testing(fake_id);
set_next_response("response");
EXPECT_EQ(CreateErrorResponse(TRUNKS_RC_IPC_ERROR),
proxy_.SendCommandAndWait("command"));
EXPECT_EQ("", last_command());
}
} // namespace trunks