| // 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"; |
| |
| const char kTestInterface4[] = "org.chromium.Test.LateInterface"; |
| |
| 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()); |
| } |
| |
| void OnInterfaceExported(bool success) { |
| // Does nothing. |
| } |
| |
| } // 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, TestRemovedInterface) { |
| // Removes the interface to be tested. |
| dbus_object_->RemoveInterface(kTestInterface3); |
| |
| 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); |
| // The response should contain error UnknownInterface since the interface has |
| // been intentionally removed. |
| EXPECT_EQ(DBUS_ERROR_UNKNOWN_INTERFACE, response->GetErrorName()); |
| } |
| |
| TEST_F(DBusObjectTest, TestInterfaceExportedLate) { |
| // Registers a new interface late. |
| dbus_object_->ExportInterfaceAsync(kTestInterface4, |
| base::Bind(&OnInterfaceExported)); |
| |
| const std::string sender{":1.2345"}; |
| dbus::MethodCall method_call(kTestInterface4, kTestMethod_WithMessage); |
| method_call.SetSerial(123); |
| method_call.SetSender(sender); |
| auto response = testing::CallMethod(*dbus_object_, &method_call); |
| // The response should contain error UnknownMethod rather than |
| // UnknownInterface since the interface has been registered late. |
| EXPECT_EQ(DBUS_ERROR_UNKNOWN_METHOD, response->GetErrorName()); |
| } |
| |
| 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 |