// 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 #include "base/bind.h" #include "base/callback_helpers.h" #include "base/files/file_descriptor_watcher_posix.h" #include "base/memory/ref_counted.h" #include "base/message_loop/message_pump_type.h" #include "base/run_loop.h" #include "base/test/task_environment.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(const RunLoopWithExpectedCount&) = delete; RunLoopWithExpectedCount& operator=(const RunLoopWithExpectedCount&) = delete; ~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_ = std::make_unique(); 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 run_loop_; int expected_quit_calls_; int actual_quit_calls_; }; // 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 = 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 = 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) { base::test::SingleThreadTaskEnvironment task_environment; // Start the D-Bus thread. base::Thread::Options thread_options; thread_options.message_pump_type = base::MessagePumpType::IO; base::Thread dbus_thread("D-Bus thread"); dbus_thread.StartWithOptions(std::move(thread_options)); // Create the bus. Bus::Options options; options.dbus_task_runner = dbus_thread.task_runner(); scoped_refptr 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 = 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_pump_type = base::MessagePumpType::IO; base::Thread dbus_thread("D-Bus thread"); dbus_thread.StartWithOptions(std::move(thread_options)); // Create the bus. Bus::Options options; options.dbus_task_runner = dbus_thread.task_runner(); scoped_refptr 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 = 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_pump_type = base::MessagePumpType::IO; base::Thread dbus_thread("D-Bus thread"); dbus_thread.StartWithOptions(std::move(thread_options)); // Create the bus. Bus::Options options; options.dbus_task_runner = dbus_thread.task_runner(); scoped_refptr 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 = 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::test::SingleThreadTaskEnvironment task_environment( base::test::SingleThreadTaskEnvironment::MainThreadType::IO); RunLoopWithExpectedCount run_loop_state; // Create the bus. Bus::Options bus_options; scoped_refptr bus = new Bus(bus_options); // Add a listener. std::string service_owner1; int num_of_owner_changes1 = 0; Bus::ServiceOwnerChangeCallback callback1 = base::BindRepeating(&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::ServiceOwnerChangeCallback callback2 = base::BindRepeating(&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 = new Bus(options); // Connection name is empty since bus is not connected. EXPECT_FALSE(bus->IsConnected()); EXPECT_TRUE(bus->GetConnectionName().empty()); // Connect bus to D-Bus. bus->Connect(); // Connection name is not empty after connection is established. EXPECT_TRUE(bus->IsConnected()); EXPECT_FALSE(bus->GetConnectionName().empty()); // Shut down synchronously. bus->ShutdownAndBlock(); EXPECT_TRUE(bus->shutdown_completed()); } } // namespace dbus