| // Copyright (c) 2012 The Chromium 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 "dbus/bus.h" | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/bind_helpers.h" | 
 | #include "base/files/file_descriptor_watcher_posix.h" | 
 | #include "base/macros.h" | 
 | #include "base/memory/ref_counted.h" | 
 | #include "base/message_loop/message_loop.h" | 
 | #include "base/run_loop.h" | 
 | #include "base/threading/thread.h" | 
 | #include "dbus/exported_object.h" | 
 | #include "dbus/object_path.h" | 
 | #include "dbus/object_proxy.h" | 
 | #include "dbus/scoped_dbus_error.h" | 
 | #include "dbus/test_service.h" | 
 |  | 
 | #include "testing/gtest/include/gtest/gtest.h" | 
 |  | 
 | namespace dbus { | 
 |  | 
 | namespace { | 
 |  | 
 | // Test helper for BusTest.ListenForServiceOwnerChange that wraps a | 
 | // base::RunLoop. At Run() time, the caller pass in the expected number of | 
 | // quit calls, and at QuitIfConditionIsSatisified() time, only quit the RunLoop | 
 | // if the expected number of quit calls have been reached. | 
 | class RunLoopWithExpectedCount { | 
 |  public: | 
 |   RunLoopWithExpectedCount() : expected_quit_calls_(0), actual_quit_calls_(0) {} | 
 |   ~RunLoopWithExpectedCount() = default; | 
 |  | 
 |   void Run(int expected_quit_calls) { | 
 |     DCHECK_EQ(0, expected_quit_calls_); | 
 |     DCHECK_EQ(0, actual_quit_calls_); | 
 |     expected_quit_calls_ = expected_quit_calls; | 
 |     run_loop_.reset(new base::RunLoop()); | 
 |     run_loop_->Run(); | 
 |   } | 
 |  | 
 |   void QuitIfConditionIsSatisified() { | 
 |     if (++actual_quit_calls_ != expected_quit_calls_) | 
 |       return; | 
 |     run_loop_->Quit(); | 
 |     expected_quit_calls_ = 0; | 
 |     actual_quit_calls_ = 0; | 
 |   } | 
 |  | 
 |  private: | 
 |   std::unique_ptr<base::RunLoop> run_loop_; | 
 |   int expected_quit_calls_; | 
 |   int actual_quit_calls_; | 
 |  | 
 |   DISALLOW_COPY_AND_ASSIGN(RunLoopWithExpectedCount); | 
 | }; | 
 |  | 
 | // Test helper for BusTest.ListenForServiceOwnerChange. | 
 | void OnServiceOwnerChanged(RunLoopWithExpectedCount* run_loop_state, | 
 |                            std::string* service_owner, | 
 |                            int* num_of_owner_changes, | 
 |                            const std::string& new_service_owner) { | 
 |   *service_owner = new_service_owner; | 
 |   ++(*num_of_owner_changes); | 
 |   run_loop_state->QuitIfConditionIsSatisified(); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | TEST(BusTest, GetObjectProxy) { | 
 |   Bus::Options options; | 
 |   scoped_refptr<Bus> bus = new Bus(options); | 
 |  | 
 |   ObjectProxy* object_proxy1 = | 
 |       bus->GetObjectProxy("org.chromium.TestService", | 
 |                           ObjectPath("/org/chromium/TestObject")); | 
 |   ASSERT_TRUE(object_proxy1); | 
 |  | 
 |   // This should return the same object. | 
 |   ObjectProxy* object_proxy2 = | 
 |       bus->GetObjectProxy("org.chromium.TestService", | 
 |                           ObjectPath("/org/chromium/TestObject")); | 
 |   ASSERT_TRUE(object_proxy2); | 
 |   EXPECT_EQ(object_proxy1, object_proxy2); | 
 |  | 
 |   // This should not. | 
 |   ObjectProxy* object_proxy3 = | 
 |       bus->GetObjectProxy( | 
 |           "org.chromium.TestService", | 
 |           ObjectPath("/org/chromium/DifferentTestObject")); | 
 |   ASSERT_TRUE(object_proxy3); | 
 |   EXPECT_NE(object_proxy1, object_proxy3); | 
 |  | 
 |   bus->ShutdownAndBlock(); | 
 | } | 
 |  | 
 | TEST(BusTest, GetObjectProxyIgnoreUnknownService) { | 
 |   Bus::Options options; | 
 |   scoped_refptr<Bus> bus = new Bus(options); | 
 |  | 
 |   ObjectProxy* object_proxy1 = | 
 |       bus->GetObjectProxyWithOptions( | 
 |           "org.chromium.TestService", | 
 |           ObjectPath("/org/chromium/TestObject"), | 
 |           ObjectProxy::IGNORE_SERVICE_UNKNOWN_ERRORS); | 
 |   ASSERT_TRUE(object_proxy1); | 
 |  | 
 |   // This should return the same object. | 
 |   ObjectProxy* object_proxy2 = | 
 |       bus->GetObjectProxyWithOptions( | 
 |           "org.chromium.TestService", | 
 |           ObjectPath("/org/chromium/TestObject"), | 
 |           ObjectProxy::IGNORE_SERVICE_UNKNOWN_ERRORS); | 
 |   ASSERT_TRUE(object_proxy2); | 
 |   EXPECT_EQ(object_proxy1, object_proxy2); | 
 |  | 
 |   // This should not. | 
 |   ObjectProxy* object_proxy3 = | 
 |       bus->GetObjectProxyWithOptions( | 
 |           "org.chromium.TestService", | 
 |           ObjectPath("/org/chromium/DifferentTestObject"), | 
 |           ObjectProxy::IGNORE_SERVICE_UNKNOWN_ERRORS); | 
 |   ASSERT_TRUE(object_proxy3); | 
 |   EXPECT_NE(object_proxy1, object_proxy3); | 
 |  | 
 |   bus->ShutdownAndBlock(); | 
 | } | 
 |  | 
 | TEST(BusTest, RemoveObjectProxy) { | 
 |   // Setup the current thread's MessageLoop. | 
 |   base::MessageLoop message_loop; | 
 |  | 
 |   // Start the D-Bus thread. | 
 |   base::Thread::Options thread_options; | 
 |   thread_options.message_loop_type = base::MessageLoop::TYPE_IO; | 
 |   base::Thread dbus_thread("D-Bus thread"); | 
 |   dbus_thread.StartWithOptions(thread_options); | 
 |  | 
 |   // Create the bus. | 
 |   Bus::Options options; | 
 |   options.dbus_task_runner = dbus_thread.task_runner(); | 
 |   scoped_refptr<Bus> bus = new Bus(options); | 
 |   ASSERT_FALSE(bus->shutdown_completed()); | 
 |  | 
 |   // Try to remove a non existant object proxy should return false. | 
 |   ASSERT_FALSE(bus->RemoveObjectProxy("org.chromium.TestService", | 
 |                                       ObjectPath("/org/chromium/TestObject"), | 
 |                                       base::DoNothing())); | 
 |  | 
 |   ObjectProxy* object_proxy1 = | 
 |       bus->GetObjectProxy("org.chromium.TestService", | 
 |                           ObjectPath("/org/chromium/TestObject")); | 
 |   ASSERT_TRUE(object_proxy1); | 
 |  | 
 |   // Increment the reference count to the object proxy to avoid destroying it | 
 |   // while removing the object. | 
 |   object_proxy1->AddRef(); | 
 |  | 
 |   // Remove the object from the bus. This will invalidate any other usage of | 
 |   // object_proxy1 other than destroy it. We keep this object for a comparison | 
 |   // at a later time. | 
 |   ASSERT_TRUE(bus->RemoveObjectProxy("org.chromium.TestService", | 
 |                                      ObjectPath("/org/chromium/TestObject"), | 
 |                                      base::DoNothing())); | 
 |  | 
 |   // This should return a different object because the first object was removed | 
 |   // from the bus, but not deleted from memory. | 
 |   ObjectProxy* object_proxy2 = | 
 |       bus->GetObjectProxy("org.chromium.TestService", | 
 |                           ObjectPath("/org/chromium/TestObject")); | 
 |   ASSERT_TRUE(object_proxy2); | 
 |  | 
 |   // Compare the new object with the first object. The first object still exists | 
 |   // thanks to the increased reference. | 
 |   EXPECT_NE(object_proxy1, object_proxy2); | 
 |  | 
 |   // Release object_proxy1. | 
 |   object_proxy1->Release(); | 
 |  | 
 |   // Shut down synchronously. | 
 |   bus->ShutdownOnDBusThreadAndBlock(); | 
 |   EXPECT_TRUE(bus->shutdown_completed()); | 
 |   dbus_thread.Stop(); | 
 | } | 
 |  | 
 | TEST(BusTest, GetExportedObject) { | 
 |   Bus::Options options; | 
 |   scoped_refptr<Bus> bus = new Bus(options); | 
 |  | 
 |   ExportedObject* object_proxy1 = | 
 |       bus->GetExportedObject(ObjectPath("/org/chromium/TestObject")); | 
 |   ASSERT_TRUE(object_proxy1); | 
 |  | 
 |   // This should return the same object. | 
 |   ExportedObject* object_proxy2 = | 
 |       bus->GetExportedObject(ObjectPath("/org/chromium/TestObject")); | 
 |   ASSERT_TRUE(object_proxy2); | 
 |   EXPECT_EQ(object_proxy1, object_proxy2); | 
 |  | 
 |   // This should not. | 
 |   ExportedObject* object_proxy3 = | 
 |       bus->GetExportedObject( | 
 |           ObjectPath("/org/chromium/DifferentTestObject")); | 
 |   ASSERT_TRUE(object_proxy3); | 
 |   EXPECT_NE(object_proxy1, object_proxy3); | 
 |  | 
 |   bus->ShutdownAndBlock(); | 
 | } | 
 |  | 
 | TEST(BusTest, UnregisterExportedObject) { | 
 |   // Start the D-Bus thread. | 
 |   base::Thread::Options thread_options; | 
 |   thread_options.message_loop_type = base::MessageLoop::TYPE_IO; | 
 |   base::Thread dbus_thread("D-Bus thread"); | 
 |   dbus_thread.StartWithOptions(thread_options); | 
 |  | 
 |   // Create the bus. | 
 |   Bus::Options options; | 
 |   options.dbus_task_runner = dbus_thread.task_runner(); | 
 |   scoped_refptr<Bus> bus = new Bus(options); | 
 |   ASSERT_FALSE(bus->shutdown_completed()); | 
 |  | 
 |   ExportedObject* object_proxy1 = | 
 |       bus->GetExportedObject(ObjectPath("/org/chromium/TestObject")); | 
 |   ASSERT_TRUE(object_proxy1); | 
 |  | 
 |   // Increment the reference count to the object proxy to avoid destroying it | 
 |   // calling UnregisterExportedObject. This ensures the dbus::ExportedObject is | 
 |   // not freed from memory. See http://crbug.com/137846 for details. | 
 |   object_proxy1->AddRef(); | 
 |  | 
 |   bus->UnregisterExportedObject(ObjectPath("/org/chromium/TestObject")); | 
 |  | 
 |   // This should return a new object because the object_proxy1 is still in | 
 |   // alloc'ed memory. | 
 |   ExportedObject* object_proxy2 = | 
 |       bus->GetExportedObject(ObjectPath("/org/chromium/TestObject")); | 
 |   ASSERT_TRUE(object_proxy2); | 
 |   EXPECT_NE(object_proxy1, object_proxy2); | 
 |  | 
 |   // Release the incremented reference. | 
 |   object_proxy1->Release(); | 
 |  | 
 |   // Shut down synchronously. | 
 |   bus->ShutdownOnDBusThreadAndBlock(); | 
 |   EXPECT_TRUE(bus->shutdown_completed()); | 
 |   dbus_thread.Stop(); | 
 | } | 
 |  | 
 | TEST(BusTest, ShutdownAndBlock) { | 
 |   Bus::Options options; | 
 |   scoped_refptr<Bus> bus = new Bus(options); | 
 |   ASSERT_FALSE(bus->shutdown_completed()); | 
 |  | 
 |   // Shut down synchronously. | 
 |   bus->ShutdownAndBlock(); | 
 |   EXPECT_TRUE(bus->shutdown_completed()); | 
 | } | 
 |  | 
 | TEST(BusTest, ShutdownAndBlockWithDBusThread) { | 
 |   // Start the D-Bus thread. | 
 |   base::Thread::Options thread_options; | 
 |   thread_options.message_loop_type = base::MessageLoop::TYPE_IO; | 
 |   base::Thread dbus_thread("D-Bus thread"); | 
 |   dbus_thread.StartWithOptions(thread_options); | 
 |  | 
 |   // Create the bus. | 
 |   Bus::Options options; | 
 |   options.dbus_task_runner = dbus_thread.task_runner(); | 
 |   scoped_refptr<Bus> bus = new Bus(options); | 
 |   ASSERT_FALSE(bus->shutdown_completed()); | 
 |  | 
 |   // Shut down synchronously. | 
 |   bus->ShutdownOnDBusThreadAndBlock(); | 
 |   EXPECT_TRUE(bus->shutdown_completed()); | 
 |   dbus_thread.Stop(); | 
 | } | 
 |  | 
 | TEST(BusTest, DoubleAddAndRemoveMatch) { | 
 |   Bus::Options options; | 
 |   scoped_refptr<Bus> bus = new Bus(options); | 
 |   ScopedDBusError error; | 
 |  | 
 |   bus->Connect(); | 
 |  | 
 |   // Adds the same rule twice. | 
 |   bus->AddMatch( | 
 |       "type='signal',interface='org.chromium.TestService',path='/'", | 
 |       error.get()); | 
 |   ASSERT_FALSE(error.is_set()); | 
 |  | 
 |   bus->AddMatch( | 
 |       "type='signal',interface='org.chromium.TestService',path='/'", | 
 |       error.get()); | 
 |   ASSERT_FALSE(error.is_set()); | 
 |  | 
 |   // Removes the same rule twice. | 
 |   ASSERT_TRUE(bus->RemoveMatch( | 
 |       "type='signal',interface='org.chromium.TestService',path='/'", | 
 |       error.get())); | 
 |   ASSERT_FALSE(error.is_set()); | 
 |  | 
 |   // The rule should be still in the bus since it was removed only once. | 
 |   // A second removal shouldn't give an error. | 
 |   ASSERT_TRUE(bus->RemoveMatch( | 
 |       "type='signal',interface='org.chromium.TestService',path='/'", | 
 |       error.get())); | 
 |   ASSERT_FALSE(error.is_set()); | 
 |  | 
 |   // A third attemp to remove the same rule should fail. | 
 |   ASSERT_FALSE(bus->RemoveMatch( | 
 |       "type='signal',interface='org.chromium.TestService',path='/'", | 
 |       error.get())); | 
 |  | 
 |   bus->ShutdownAndBlock(); | 
 | } | 
 |  | 
 | TEST(BusTest, ListenForServiceOwnerChange) { | 
 |   base::MessageLoopForIO message_loop; | 
 |  | 
 |   // This enables FileDescriptorWatcher, which is required by dbus::Watch. | 
 |   base::FileDescriptorWatcher file_descriptor_watcher(&message_loop); | 
 |  | 
 |   RunLoopWithExpectedCount run_loop_state; | 
 |  | 
 |   // Create the bus. | 
 |   Bus::Options bus_options; | 
 |   scoped_refptr<Bus> bus = new Bus(bus_options); | 
 |  | 
 |   // Add a listener. | 
 |   std::string service_owner1; | 
 |   int num_of_owner_changes1 = 0; | 
 |   Bus::GetServiceOwnerCallback callback1 = | 
 |       base::Bind(&OnServiceOwnerChanged, | 
 |                  &run_loop_state, | 
 |                  &service_owner1, | 
 |                  &num_of_owner_changes1); | 
 |   bus->ListenForServiceOwnerChange("org.chromium.TestService", callback1); | 
 |   // This should be a no-op. | 
 |   bus->ListenForServiceOwnerChange("org.chromium.TestService", callback1); | 
 |   base::RunLoop().RunUntilIdle(); | 
 |  | 
 |   // Nothing has happened yet. Check initial state. | 
 |   EXPECT_TRUE(service_owner1.empty()); | 
 |   EXPECT_EQ(0, num_of_owner_changes1); | 
 |  | 
 |   // Make an ownership change. | 
 |   ASSERT_TRUE(bus->RequestOwnershipAndBlock("org.chromium.TestService", | 
 |                                             Bus::REQUIRE_PRIMARY)); | 
 |   run_loop_state.Run(1); | 
 |  | 
 |   { | 
 |     // Get the current service owner and check to make sure the listener got | 
 |     // the right value. | 
 |     std::string current_service_owner = | 
 |         bus->GetServiceOwnerAndBlock("org.chromium.TestService", | 
 |                                      Bus::REPORT_ERRORS); | 
 |     ASSERT_FALSE(current_service_owner.empty()); | 
 |  | 
 |     // Make sure the listener heard about the new owner. | 
 |     EXPECT_EQ(current_service_owner, service_owner1); | 
 |  | 
 |     // Test the second ListenForServiceOwnerChange() above is indeed a no-op. | 
 |     EXPECT_EQ(1, num_of_owner_changes1); | 
 |   } | 
 |  | 
 |   // Add a second listener. | 
 |   std::string service_owner2; | 
 |   int num_of_owner_changes2 = 0; | 
 |   Bus::GetServiceOwnerCallback callback2 = | 
 |       base::Bind(&OnServiceOwnerChanged, | 
 |                  &run_loop_state, | 
 |                  &service_owner2, | 
 |                  &num_of_owner_changes2); | 
 |   bus->ListenForServiceOwnerChange("org.chromium.TestService", callback2); | 
 |   base::RunLoop().RunUntilIdle(); | 
 |  | 
 |   // Release the ownership and make sure the service owner listeners fire with | 
 |   // the right values and the right number of times. | 
 |   ASSERT_TRUE(bus->ReleaseOwnership("org.chromium.TestService")); | 
 |   run_loop_state.Run(2); | 
 |  | 
 |   EXPECT_TRUE(service_owner1.empty()); | 
 |   EXPECT_TRUE(service_owner2.empty()); | 
 |   EXPECT_EQ(2, num_of_owner_changes1); | 
 |   EXPECT_EQ(1, num_of_owner_changes2); | 
 |  | 
 |   // Unlisten so shutdown can proceed correctly. | 
 |   bus->UnlistenForServiceOwnerChange("org.chromium.TestService", callback1); | 
 |   bus->UnlistenForServiceOwnerChange("org.chromium.TestService", callback2); | 
 |   base::RunLoop().RunUntilIdle(); | 
 |  | 
 |   // Shut down synchronously. | 
 |   bus->ShutdownAndBlock(); | 
 |   EXPECT_TRUE(bus->shutdown_completed()); | 
 | } | 
 |  | 
 | TEST(BusTest, GetConnectionName) { | 
 |   Bus::Options options; | 
 |   scoped_refptr<Bus> bus = new Bus(options); | 
 |  | 
 |   // Connection name is empty since bus is not connected. | 
 |   EXPECT_FALSE(bus->is_connected()); | 
 |   EXPECT_TRUE(bus->GetConnectionName().empty()); | 
 |  | 
 |   // Connect bus to D-Bus. | 
 |   bus->Connect(); | 
 |  | 
 |   // Connection name is not empty after connection is established. | 
 |   EXPECT_TRUE(bus->is_connected()); | 
 |   EXPECT_FALSE(bus->GetConnectionName().empty()); | 
 |  | 
 |   // Shut down synchronously. | 
 |   bus->ShutdownAndBlock(); | 
 |   EXPECT_TRUE(bus->shutdown_completed()); | 
 | } | 
 |  | 
 | }  // namespace dbus |