普通文本  |  393行  |  13.94 KB

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

#include <brillo/dbus/dbus_object.h>

#include <memory>

#include <base/bind.h>
#include <brillo/dbus/dbus_object_test_helpers.h>
#include <brillo/dbus/mock_exported_object_manager.h>
#include <dbus/message.h>
#include <dbus/property.h>
#include <dbus/object_path.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_exported_object.h>

using ::testing::AnyNumber;
using ::testing::Return;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::_;

namespace brillo {
namespace dbus_utils {

namespace {

const char kMethodsExportedOn[] = "/export";

const char kTestInterface1[] = "org.chromium.Test.MathInterface";
const char kTestMethod_Add[] = "Add";
const char kTestMethod_Negate[] = "Negate";
const char kTestMethod_Positive[] = "Positive";
const char kTestMethod_AddSubtract[] = "AddSubtract";

const char kTestInterface2[] = "org.chromium.Test.StringInterface";
const char kTestMethod_StrLen[] = "StrLen";
const char kTestMethod_CheckNonEmpty[] = "CheckNonEmpty";

const char kTestInterface3[] = "org.chromium.Test.NoOpInterface";
const char kTestMethod_NoOp[] = "NoOp";
const char kTestMethod_WithMessage[] = "TestWithMessage";
const char kTestMethod_WithMessageAsync[] = "TestWithMessageAsync";

struct Calc {
  int Add(int x, int y) { return x + y; }
  int Negate(int x) { return -x; }
  void Positive(std::unique_ptr<DBusMethodResponse<double>> response,
                double x) {
    if (x >= 0.0) {
      response->Return(x);
      return;
    }
    ErrorPtr error;
    Error::AddTo(&error, FROM_HERE, "test", "not_positive",
                 "Negative value passed in");
    response->ReplyWithError(error.get());
  }
  void AddSubtract(int x, int y, int* sum, int* diff) {
    *sum = x + y;
    *diff = x - y;
  }
};

int StrLen(const std::string& str) {
  return str.size();
}

bool CheckNonEmpty(ErrorPtr* error, const std::string& str) {
  if (!str.empty())
    return true;
  Error::AddTo(error, FROM_HERE, "test", "string_empty", "String is empty");
  return false;
}

void NoOp() {}

bool TestWithMessage(ErrorPtr* /* error */,
                     dbus::Message* message,
                     std::string* str) {
  *str = message->GetSender();
  return true;
}

void TestWithMessageAsync(
    std::unique_ptr<DBusMethodResponse<std::string>> response,
    dbus::Message* message) {
  response->Return(message->GetSender());
}

}  // namespace

class DBusObjectTest : public ::testing::Test {
 public:
  virtual void SetUp() {
    dbus::Bus::Options options;
    options.bus_type = dbus::Bus::SYSTEM;
    bus_ = new dbus::MockBus(options);
    // By default, don't worry about threading assertions.
    EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber());
    EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber());
    // Use a mock exported object.
    const dbus::ObjectPath kMethodsExportedOnPath{
        std::string{kMethodsExportedOn}};
    mock_exported_object_ =
        new dbus::MockExportedObject(bus_.get(), kMethodsExportedOnPath);
    EXPECT_CALL(*bus_, GetExportedObject(kMethodsExportedOnPath))
        .Times(AnyNumber())
        .WillRepeatedly(Return(mock_exported_object_.get()));
    EXPECT_CALL(*mock_exported_object_, ExportMethod(_, _, _, _))
        .Times(AnyNumber());
    EXPECT_CALL(*mock_exported_object_, Unregister()).Times(1);

    dbus_object_ = std::unique_ptr<DBusObject>(
        new DBusObject(nullptr, bus_, kMethodsExportedOnPath));

    DBusInterface* itf1 = dbus_object_->AddOrGetInterface(kTestInterface1);
    itf1->AddSimpleMethodHandler(
        kTestMethod_Add, base::Unretained(&calc_), &Calc::Add);
    itf1->AddSimpleMethodHandler(
        kTestMethod_Negate, base::Unretained(&calc_), &Calc::Negate);
    itf1->AddMethodHandler(
        kTestMethod_Positive, base::Unretained(&calc_), &Calc::Positive);
    itf1->AddSimpleMethodHandler(
        kTestMethod_AddSubtract, base::Unretained(&calc_), &Calc::AddSubtract);
    DBusInterface* itf2 = dbus_object_->AddOrGetInterface(kTestInterface2);
    itf2->AddSimpleMethodHandler(kTestMethod_StrLen, StrLen);
    itf2->AddSimpleMethodHandlerWithError(kTestMethod_CheckNonEmpty,
                                          CheckNonEmpty);
    DBusInterface* itf3 = dbus_object_->AddOrGetInterface(kTestInterface3);
    base::Callback<void()> noop_callback = base::Bind(NoOp);
    itf3->AddSimpleMethodHandler(kTestMethod_NoOp, noop_callback);
    itf3->AddSimpleMethodHandlerWithErrorAndMessage(
        kTestMethod_WithMessage, base::Bind(&TestWithMessage));
    itf3->AddMethodHandlerWithMessage(kTestMethod_WithMessageAsync,
                                      base::Bind(&TestWithMessageAsync));

    dbus_object_->RegisterAsync(
        AsyncEventSequencer::GetDefaultCompletionAction());
  }

  void ExpectError(dbus::Response* response, const std::string& expected_code) {
    EXPECT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
    EXPECT_EQ(expected_code, response->GetErrorName());
  }

  scoped_refptr<dbus::MockBus> bus_;
  scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
  std::unique_ptr<DBusObject> dbus_object_;
  Calc calc_;
};

TEST_F(DBusObjectTest, Add) {
  dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendInt32(2);
  writer.AppendInt32(3);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  int result;
  ASSERT_TRUE(reader.PopInt32(&result));
  ASSERT_FALSE(reader.HasMoreData());
  ASSERT_EQ(5, result);
}

TEST_F(DBusObjectTest, Negate) {
  dbus::MethodCall method_call(kTestInterface1, kTestMethod_Negate);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendInt32(98765);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  int result;
  ASSERT_TRUE(reader.PopInt32(&result));
  ASSERT_FALSE(reader.HasMoreData());
  ASSERT_EQ(-98765, result);
}

TEST_F(DBusObjectTest, PositiveSuccess) {
  dbus::MethodCall method_call(kTestInterface1, kTestMethod_Positive);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendDouble(17.5);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  double result;
  ASSERT_TRUE(reader.PopDouble(&result));
  ASSERT_FALSE(reader.HasMoreData());
  ASSERT_DOUBLE_EQ(17.5, result);
}

TEST_F(DBusObjectTest, PositiveFailure) {
  dbus::MethodCall method_call(kTestInterface1, kTestMethod_Positive);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendDouble(-23.2);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  ExpectError(response.get(), DBUS_ERROR_FAILED);
}

TEST_F(DBusObjectTest, AddSubtract) {
  dbus::MethodCall method_call(kTestInterface1, kTestMethod_AddSubtract);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendInt32(2);
  writer.AppendInt32(3);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  int sum = 0, diff = 0;
  ASSERT_TRUE(reader.PopInt32(&sum));
  ASSERT_TRUE(reader.PopInt32(&diff));
  ASSERT_FALSE(reader.HasMoreData());
  EXPECT_EQ(5, sum);
  EXPECT_EQ(-1, diff);
}

TEST_F(DBusObjectTest, StrLen0) {
  dbus::MethodCall method_call(kTestInterface2, kTestMethod_StrLen);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendString("");
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  int result;
  ASSERT_TRUE(reader.PopInt32(&result));
  ASSERT_FALSE(reader.HasMoreData());
  ASSERT_EQ(0, result);
}

TEST_F(DBusObjectTest, StrLen4) {
  dbus::MethodCall method_call(kTestInterface2, kTestMethod_StrLen);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendString("test");
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  int result;
  ASSERT_TRUE(reader.PopInt32(&result));
  ASSERT_FALSE(reader.HasMoreData());
  ASSERT_EQ(4, result);
}

TEST_F(DBusObjectTest, CheckNonEmpty_Success) {
  dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendString("test");
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  ASSERT_EQ(dbus::Message::MESSAGE_METHOD_RETURN, response->GetMessageType());
  dbus::MessageReader reader(response.get());
  EXPECT_FALSE(reader.HasMoreData());
}

TEST_F(DBusObjectTest, CheckNonEmpty_Failure) {
  dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendString("");
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
  ErrorPtr error;
  ExtractMethodCallResults(response.get(), &error);
  ASSERT_NE(nullptr, error.get());
  EXPECT_EQ("test", error->GetDomain());
  EXPECT_EQ("string_empty", error->GetCode());
  EXPECT_EQ("String is empty", error->GetMessage());
}

TEST_F(DBusObjectTest, CheckNonEmpty_MissingParams) {
  dbus::MethodCall method_call(kTestInterface2, kTestMethod_CheckNonEmpty);
  method_call.SetSerial(123);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  ASSERT_EQ(dbus::Message::MESSAGE_ERROR, response->GetMessageType());
  dbus::MessageReader reader(response.get());
  std::string message;
  ASSERT_TRUE(reader.PopString(&message));
  EXPECT_EQ(DBUS_ERROR_INVALID_ARGS, response->GetErrorName());
  EXPECT_EQ("Too few parameters in a method call", message);
  EXPECT_FALSE(reader.HasMoreData());
}

TEST_F(DBusObjectTest, NoOp) {
  dbus::MethodCall method_call(kTestInterface3, kTestMethod_NoOp);
  method_call.SetSerial(123);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  ASSERT_FALSE(reader.HasMoreData());
}

TEST_F(DBusObjectTest, TestWithMessage) {
  const std::string sender{":1.2345"};
  dbus::MethodCall method_call(kTestInterface3, kTestMethod_WithMessage);
  method_call.SetSerial(123);
  method_call.SetSender(sender);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  std::string message;
  ASSERT_TRUE(reader.PopString(&message));
  ASSERT_FALSE(reader.HasMoreData());
  EXPECT_EQ(sender, message);
}

TEST_F(DBusObjectTest, TestWithMessageAsync) {
  const std::string sender{":6.7890"};
  dbus::MethodCall method_call(kTestInterface3, kTestMethod_WithMessageAsync);
  method_call.SetSerial(123);
  method_call.SetSender(sender);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  std::string message;
  ASSERT_TRUE(reader.PopString(&message));
  ASSERT_FALSE(reader.HasMoreData());
  EXPECT_EQ(sender, message);
}

TEST_F(DBusObjectTest, TooFewParams) {
  dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendInt32(2);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS);
}

TEST_F(DBusObjectTest, TooManyParams) {
  dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendInt32(1);
  writer.AppendInt32(2);
  writer.AppendInt32(3);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS);
}

TEST_F(DBusObjectTest, ParamTypeMismatch) {
  dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendInt32(1);
  writer.AppendBool(false);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  ExpectError(response.get(), DBUS_ERROR_INVALID_ARGS);
}

TEST_F(DBusObjectTest, ParamAsVariant) {
  dbus::MethodCall method_call(kTestInterface1, kTestMethod_Add);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendVariantOfInt32(10);
  writer.AppendVariantOfInt32(3);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  dbus::MessageReader reader(response.get());
  int result;
  ASSERT_TRUE(reader.PopInt32(&result));
  ASSERT_FALSE(reader.HasMoreData());
  ASSERT_EQ(13, result);
}

TEST_F(DBusObjectTest, UnknownMethod) {
  dbus::MethodCall method_call(kTestInterface2, kTestMethod_Add);
  method_call.SetSerial(123);
  dbus::MessageWriter writer(&method_call);
  writer.AppendInt32(1);
  writer.AppendBool(false);
  auto response = testing::CallMethod(*dbus_object_, &method_call);
  ExpectError(response.get(), DBUS_ERROR_UNKNOWN_METHOD);
}

TEST_F(DBusObjectTest, ShouldReleaseOnlyClaimedInterfaces) {
  const dbus::ObjectPath kObjectManagerPath{std::string{"/"}};
  const dbus::ObjectPath kMethodsExportedOnPath{
      std::string{kMethodsExportedOn}};
  MockExportedObjectManager mock_object_manager{bus_, kObjectManagerPath};
  dbus_object_ = std::unique_ptr<DBusObject>(
      new DBusObject(&mock_object_manager, bus_, kMethodsExportedOnPath));
  EXPECT_CALL(mock_object_manager, ClaimInterface(_, _, _)).Times(0);
  EXPECT_CALL(mock_object_manager, ReleaseInterface(_, _)).Times(0);
  DBusInterface* itf1 = dbus_object_->AddOrGetInterface(kTestInterface1);
  itf1->AddSimpleMethodHandler(
      kTestMethod_Add, base::Unretained(&calc_), &Calc::Add);
  // When we tear down our DBusObject, it should release only interfaces it has
  // previously claimed.  This prevents a check failing inside the
  // ExportedObjectManager.  Since no interfaces have finished exporting
  // handlers, nothing should be released.
  dbus_object_.reset();
}

}  // namespace dbus_utils
}  // namespace brillo