| // 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_method_invoker.h> |
| |
| #include <string> |
| |
| #include <base/files/scoped_file.h> |
| #include <brillo/bind_lambda.h> |
| #include <dbus/mock_bus.h> |
| #include <dbus/mock_object_proxy.h> |
| #include <dbus/scoped_dbus_error.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "brillo/dbus/test.pb.h" |
| |
| using testing::AnyNumber; |
| using testing::InSequence; |
| using testing::Invoke; |
| using testing::Return; |
| using testing::_; |
| |
| using dbus::MessageReader; |
| using dbus::MessageWriter; |
| using dbus::Response; |
| |
| namespace { |
| |
| void SuccessCallback(const std::string& expected_result, |
| int* counter, |
| const std::string& actual_result) { |
| (*counter)++; |
| EXPECT_EQ(expected_result, actual_result); |
| } |
| |
| void SimpleSuccessCallback(int* counter, const std::string& result) { |
| (*counter)++; |
| } |
| |
| void ErrorCallback(const std::string& domain, |
| const std::string& code, |
| const std::string& message, |
| int* counter, |
| brillo::Error* error) { |
| (*counter)++; |
| ASSERT_NE(nullptr, error); |
| EXPECT_EQ(domain, error->GetDomain()); |
| EXPECT_EQ(code, error->GetCode()); |
| EXPECT_EQ(message, error->GetMessage()); |
| } |
| |
| void SimpleErrorCallback(int* counter, brillo::Error* error) { |
| (*counter)++; |
| } |
| |
| } // namespace |
| |
| namespace brillo { |
| namespace dbus_utils { |
| |
| const char kTestPath[] = "/test/path"; |
| const char kTestServiceName[] = "org.test.Object"; |
| const char kTestInterface[] = "org.test.Object.TestInterface"; |
| const char kTestMethod1[] = "TestMethod1"; |
| const char kTestMethod2[] = "TestMethod2"; |
| const char kTestMethod3[] = "TestMethod3"; |
| const char kTestMethod4[] = "TestMethod4"; |
| |
| class DBusMethodInvokerTest : public testing::Test { |
| public: |
| void SetUp() override { |
| 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. |
| mock_object_proxy_ = new dbus::MockObjectProxy( |
| bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath)); |
| EXPECT_CALL(*bus_, |
| GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath))) |
| .WillRepeatedly(Return(mock_object_proxy_.get())); |
| int def_timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT; |
| EXPECT_CALL(*mock_object_proxy_, |
| MockCallMethodAndBlockWithErrorDetails(_, def_timeout_ms, _)) |
| .WillRepeatedly(Invoke(this, &DBusMethodInvokerTest::CreateResponse)); |
| } |
| |
| void TearDown() override { bus_ = nullptr; } |
| |
| Response* CreateResponse(dbus::MethodCall* method_call, |
| int /* timeout_ms */, |
| dbus::ScopedDBusError* dbus_error) { |
| if (method_call->GetInterface() == kTestInterface) { |
| if (method_call->GetMember() == kTestMethod1) { |
| MessageReader reader(method_call); |
| int v1, v2; |
| // Input: two ints. |
| // Output: sum of the ints converted to string. |
| if (reader.PopInt32(&v1) && reader.PopInt32(&v2)) { |
| auto response = Response::CreateEmpty(); |
| MessageWriter writer(response.get()); |
| writer.AppendString(std::to_string(v1 + v2)); |
| return response.release(); |
| } |
| } else if (method_call->GetMember() == kTestMethod2) { |
| method_call->SetSerial(123); |
| dbus_set_error(dbus_error->get(), "org.MyError", "My error message"); |
| return nullptr; |
| } else if (method_call->GetMember() == kTestMethod3) { |
| MessageReader reader(method_call); |
| dbus_utils_test::TestMessage msg; |
| if (PopValueFromReader(&reader, &msg)) { |
| auto response = Response::CreateEmpty(); |
| MessageWriter writer(response.get()); |
| AppendValueToWriter(&writer, msg); |
| return response.release(); |
| } |
| } else if (method_call->GetMember() == kTestMethod4) { |
| method_call->SetSerial(123); |
| MessageReader reader(method_call); |
| base::ScopedFD fd; |
| if (reader.PopFileDescriptor(&fd)) { |
| auto response = Response::CreateEmpty(); |
| MessageWriter writer(response.get()); |
| writer.AppendFileDescriptor(fd.get()); |
| return response.release(); |
| } |
| } |
| } |
| |
| LOG(ERROR) << "Unexpected method call: " << method_call->ToString(); |
| return nullptr; |
| } |
| |
| std::string CallTestMethod(int v1, int v2) { |
| std::unique_ptr<dbus::Response> response = |
| brillo::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), |
| kTestInterface, kTestMethod1, |
| nullptr, v1, v2); |
| EXPECT_NE(nullptr, response.get()); |
| std::string result; |
| using brillo::dbus_utils::ExtractMethodCallResults; |
| EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &result)); |
| return result; |
| } |
| |
| dbus_utils_test::TestMessage CallProtobufTestMethod( |
| const dbus_utils_test::TestMessage& message) { |
| std::unique_ptr<dbus::Response> response = |
| brillo::dbus_utils::CallMethodAndBlock(mock_object_proxy_.get(), |
| kTestInterface, kTestMethod3, |
| nullptr, message); |
| EXPECT_NE(nullptr, response.get()); |
| dbus_utils_test::TestMessage result; |
| using brillo::dbus_utils::ExtractMethodCallResults; |
| EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &result)); |
| return result; |
| } |
| |
| // Sends a file descriptor received over D-Bus back to the caller using the |
| // new types. |
| base::ScopedFD EchoFD(int fd_in) { |
| std::unique_ptr<dbus::Response> response = |
| brillo::dbus_utils::CallMethodAndBlock( |
| mock_object_proxy_.get(), kTestInterface, kTestMethod4, |
| nullptr, brillo::dbus_utils::FileDescriptor{fd_in}); |
| EXPECT_NE(nullptr, response.get()); |
| base::ScopedFD fd_out; |
| using brillo::dbus_utils::ExtractMethodCallResults; |
| EXPECT_TRUE(ExtractMethodCallResults(response.get(), nullptr, &fd_out)); |
| return fd_out; |
| } |
| scoped_refptr<dbus::MockBus> bus_; |
| scoped_refptr<dbus::MockObjectProxy> mock_object_proxy_; |
| }; |
| |
| TEST_F(DBusMethodInvokerTest, TestSuccess) { |
| EXPECT_EQ("4", CallTestMethod(2, 2)); |
| EXPECT_EQ("10", CallTestMethod(3, 7)); |
| EXPECT_EQ("-4", CallTestMethod(13, -17)); |
| } |
| |
| TEST_F(DBusMethodInvokerTest, TestFailure) { |
| brillo::ErrorPtr error; |
| std::unique_ptr<dbus::Response> response = |
| brillo::dbus_utils::CallMethodAndBlock( |
| mock_object_proxy_.get(), kTestInterface, kTestMethod2, &error); |
| EXPECT_EQ(nullptr, response.get()); |
| EXPECT_EQ(brillo::errors::dbus::kDomain, error->GetDomain()); |
| EXPECT_EQ("org.MyError", error->GetCode()); |
| EXPECT_EQ("My error message", error->GetMessage()); |
| } |
| |
| TEST_F(DBusMethodInvokerTest, TestProtobuf) { |
| dbus_utils_test::TestMessage test_message; |
| test_message.set_foo(123); |
| test_message.set_bar("bar"); |
| |
| dbus_utils_test::TestMessage resp = CallProtobufTestMethod(test_message); |
| |
| EXPECT_EQ(123, resp.foo()); |
| EXPECT_EQ("bar", resp.bar()); |
| } |
| |
| TEST_F(DBusMethodInvokerTest, TestFileDescriptors) { |
| // Passing a file descriptor over D-Bus would effectively duplicate the fd. |
| // So the resulting file descriptor value would be different but it still |
| // should be valid. |
| int fd_stdin = 0; |
| base::ScopedFD out_fd = EchoFD(fd_stdin); |
| EXPECT_NE(fd_stdin, out_fd.get()); |
| EXPECT_TRUE(out_fd.is_valid()); |
| int fd_stdout = 1; |
| out_fd = EchoFD(fd_stdout); |
| EXPECT_NE(fd_stdout, out_fd.get()); |
| EXPECT_TRUE(out_fd.is_valid()); |
| int fd_stderr = 2; |
| out_fd = EchoFD(fd_stderr); |
| EXPECT_NE(fd_stderr, out_fd.get()); |
| EXPECT_TRUE(out_fd.is_valid()); |
| } |
| |
| ////////////////////////////////////////////////////////////////////////////// |
| // Asynchronous method invocation support |
| |
| class AsyncDBusMethodInvokerTest : public testing::Test { |
| public: |
| void SetUp() override { |
| 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. |
| mock_object_proxy_ = new dbus::MockObjectProxy( |
| bus_.get(), kTestServiceName, dbus::ObjectPath(kTestPath)); |
| EXPECT_CALL(*bus_, |
| GetObjectProxy(kTestServiceName, dbus::ObjectPath(kTestPath))) |
| .WillRepeatedly(Return(mock_object_proxy_.get())); |
| int def_timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT; |
| EXPECT_CALL(*mock_object_proxy_, |
| CallMethodWithErrorCallback(_, def_timeout_ms, _, _)) |
| .WillRepeatedly(Invoke(this, &AsyncDBusMethodInvokerTest::HandleCall)); |
| } |
| |
| void TearDown() override { bus_ = nullptr; } |
| |
| void HandleCall(dbus::MethodCall* method_call, |
| int /* timeout_ms */, |
| dbus::ObjectProxy::ResponseCallback success_callback, |
| dbus::ObjectProxy::ErrorCallback error_callback) { |
| if (method_call->GetInterface() == kTestInterface) { |
| if (method_call->GetMember() == kTestMethod1) { |
| MessageReader reader(method_call); |
| int v1, v2; |
| // Input: two ints. |
| // Output: sum of the ints converted to string. |
| if (reader.PopInt32(&v1) && reader.PopInt32(&v2)) { |
| auto response = Response::CreateEmpty(); |
| MessageWriter writer(response.get()); |
| writer.AppendString(std::to_string(v1 + v2)); |
| success_callback.Run(response.get()); |
| } |
| return; |
| } else if (method_call->GetMember() == kTestMethod2) { |
| method_call->SetSerial(123); |
| auto error_response = dbus::ErrorResponse::FromMethodCall( |
| method_call, "org.MyError", "My error message"); |
| error_callback.Run(error_response.get()); |
| return; |
| } |
| } |
| |
| LOG(FATAL) << "Unexpected method call: " << method_call->ToString(); |
| } |
| |
| scoped_refptr<dbus::MockBus> bus_; |
| scoped_refptr<dbus::MockObjectProxy> mock_object_proxy_; |
| }; |
| |
| TEST_F(AsyncDBusMethodInvokerTest, TestSuccess) { |
| int error_count = 0; |
| int success_count = 0; |
| brillo::dbus_utils::CallMethod( |
| mock_object_proxy_.get(), |
| kTestInterface, |
| kTestMethod1, |
| base::Bind(SuccessCallback, "4", &success_count), |
| base::Bind(SimpleErrorCallback, &error_count), |
| 2, 2); |
| brillo::dbus_utils::CallMethod( |
| mock_object_proxy_.get(), |
| kTestInterface, |
| kTestMethod1, |
| base::Bind(SuccessCallback, "10", &success_count), |
| base::Bind(SimpleErrorCallback, &error_count), |
| 3, 7); |
| brillo::dbus_utils::CallMethod( |
| mock_object_proxy_.get(), |
| kTestInterface, |
| kTestMethod1, |
| base::Bind(SuccessCallback, "-4", &success_count), |
| base::Bind(SimpleErrorCallback, &error_count), |
| 13, -17); |
| EXPECT_EQ(0, error_count); |
| EXPECT_EQ(3, success_count); |
| } |
| |
| TEST_F(AsyncDBusMethodInvokerTest, TestFailure) { |
| int error_count = 0; |
| int success_count = 0; |
| brillo::dbus_utils::CallMethod( |
| mock_object_proxy_.get(), |
| kTestInterface, |
| kTestMethod2, |
| base::Bind(SimpleSuccessCallback, &success_count), |
| base::Bind(ErrorCallback, |
| brillo::errors::dbus::kDomain, |
| "org.MyError", |
| "My error message", |
| &error_count), |
| 2, 2); |
| EXPECT_EQ(1, error_count); |
| EXPECT_EQ(0, success_count); |
| } |
| |
| } // namespace dbus_utils |
| } // namespace brillo |