diff options
Diffstat (limited to 'dbus/bus_unittest.cc')
-rw-r--r-- | dbus/bus_unittest.cc | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/dbus/bus_unittest.cc b/dbus/bus_unittest.cc new file mode 100644 index 0000000000..f00d1e9374 --- /dev/null +++ b/dbus/bus_unittest.cc @@ -0,0 +1,420 @@ +// 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 |