// Copyright (c) 2013 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/object_manager.h" #include #include #include #include #include "base/bind.h" #include "base/message_loop/message_loop.h" #include "base/run_loop.h" #include "base/threading/thread.h" #include "base/threading/thread_restrictions.h" #include "dbus/bus.h" #include "dbus/object_path.h" #include "dbus/object_proxy.h" #include "dbus/property.h" #include "dbus/test_service.h" #include "testing/gtest/include/gtest/gtest.h" namespace dbus { // The object manager test exercises the asynchronous APIs in ObjectManager, // and by extension PropertySet and Property<>. class ObjectManagerTest : public testing::Test, public ObjectManager::Interface { public: ObjectManagerTest() : timeout_expired_(false) { } struct Properties : public PropertySet { Property name; Property version; Property > methods; Property > objects; Properties(ObjectProxy* object_proxy, const std::string& interface_name, PropertyChangedCallback property_changed_callback) : PropertySet(object_proxy, interface_name, property_changed_callback) { RegisterProperty("Name", &name); RegisterProperty("Version", &version); RegisterProperty("Methods", &methods); RegisterProperty("Objects", &objects); } }; PropertySet* CreateProperties(ObjectProxy* object_proxy, const ObjectPath& object_path, const std::string& interface_name) override { Properties* properties = new Properties( object_proxy, interface_name, base::Bind(&ObjectManagerTest::OnPropertyChanged, base::Unretained(this), object_path)); return static_cast(properties); } void SetUp() override { // Make the main thread not to allow IO. base::ThreadRestrictions::SetIOAllowed(false); // Start the D-Bus thread. dbus_thread_.reset(new base::Thread("D-Bus Thread")); base::Thread::Options thread_options; thread_options.message_loop_type = base::MessageLoop::TYPE_IO; ASSERT_TRUE(dbus_thread_->StartWithOptions(thread_options)); // Start the test service, using the D-Bus thread. TestService::Options options; options.dbus_task_runner = dbus_thread_->task_runner(); test_service_.reset(new TestService(options)); ASSERT_TRUE(test_service_->StartService()); ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted()); ASSERT_TRUE(test_service_->HasDBusThread()); // Create the client, using the D-Bus thread. Bus::Options bus_options; bus_options.bus_type = Bus::SESSION; bus_options.connection_type = Bus::PRIVATE; bus_options.dbus_task_runner = dbus_thread_->task_runner(); bus_ = new Bus(bus_options); ASSERT_TRUE(bus_->HasDBusThread()); object_manager_ = bus_->GetObjectManager( test_service_->service_name(), ObjectPath("/org/chromium/TestService")); object_manager_->RegisterInterface("org.chromium.TestInterface", this); WaitForObject(); } void TearDown() override { bus_->ShutdownOnDBusThreadAndBlock(); // Shut down the service. test_service_->ShutdownAndBlock(); // Reset to the default. base::ThreadRestrictions::SetIOAllowed(true); // Stopping a thread is considered an IO operation, so do this after // allowing IO. test_service_->Stop(); base::RunLoop().RunUntilIdle(); } void MethodCallback(Response* response) { method_callback_called_ = true; run_loop_->Quit(); } // Called from the PropertiesChangedAsObjectsReceived test case. The test will // not run the message loop if it receives the expected PropertiesChanged // signal before the timeout. This method immediately fails the test. void PropertiesChangedTestTimeout() { timeout_expired_ = true; run_loop_->Quit(); FAIL() << "Never received PropertiesChanged"; } protected: // Called when an object is added. void ObjectAdded(const ObjectPath& object_path, const std::string& interface_name) override { added_objects_.push_back(std::make_pair(object_path, interface_name)); run_loop_->Quit(); } // Called when an object is removed. void ObjectRemoved(const ObjectPath& object_path, const std::string& interface_name) override { removed_objects_.push_back(std::make_pair(object_path, interface_name)); run_loop_->Quit(); } // Called when a property value is updated. void OnPropertyChanged(const ObjectPath& object_path, const std::string& name) { // Store the value of the "Name" property if that's the one that // changed. Properties* properties = static_cast( object_manager_->GetProperties( object_path, "org.chromium.TestInterface")); if (name == properties->name.name()) last_name_value_ = properties->name.value(); // Store the updated property. updated_properties_.push_back(name); run_loop_->Quit(); } static const size_t kExpectedObjects = 1; static const size_t kExpectedProperties = 4; void WaitForObject() { while (added_objects_.size() < kExpectedObjects || updated_properties_.size() < kExpectedProperties) { run_loop_.reset(new base::RunLoop); run_loop_->Run(); } for (size_t i = 0; i < kExpectedObjects; ++i) added_objects_.erase(added_objects_.begin()); for (size_t i = 0; i < kExpectedProperties; ++i) updated_properties_.erase(updated_properties_.begin()); } void WaitForRemoveObject() { while (removed_objects_.size() < kExpectedObjects) { run_loop_.reset(new base::RunLoop); run_loop_->Run(); } for (size_t i = 0; i < kExpectedObjects; ++i) removed_objects_.erase(removed_objects_.begin()); } void WaitForMethodCallback() { run_loop_.reset(new base::RunLoop); run_loop_->Run(); method_callback_called_ = false; } void PerformAction(const std::string& action, const ObjectPath& object_path) { ObjectProxy* object_proxy = bus_->GetObjectProxy( test_service_->service_name(), ObjectPath("/org/chromium/TestObject")); MethodCall method_call("org.chromium.TestInterface", "PerformAction"); MessageWriter writer(&method_call); writer.AppendString(action); writer.AppendObjectPath(object_path); object_proxy->CallMethod(&method_call, ObjectProxy::TIMEOUT_USE_DEFAULT, base::Bind(&ObjectManagerTest::MethodCallback, base::Unretained(this))); WaitForMethodCallback(); } base::MessageLoop message_loop_; scoped_ptr run_loop_; scoped_ptr dbus_thread_; scoped_refptr bus_; ObjectManager* object_manager_; scoped_ptr test_service_; std::string last_name_value_; bool timeout_expired_; std::vector > added_objects_; std::vector > removed_objects_; std::vector updated_properties_; bool method_callback_called_; }; TEST_F(ObjectManagerTest, InitialObject) { ObjectProxy* object_proxy = object_manager_->GetObjectProxy( ObjectPath("/org/chromium/TestObject")); EXPECT_TRUE(object_proxy != NULL); Properties* properties = static_cast( object_manager_->GetProperties(ObjectPath("/org/chromium/TestObject"), "org.chromium.TestInterface")); EXPECT_TRUE(properties != NULL); EXPECT_EQ("TestService", properties->name.value()); EXPECT_EQ(10, properties->version.value()); std::vector methods = properties->methods.value(); ASSERT_EQ(4U, methods.size()); EXPECT_EQ("Echo", methods[0]); EXPECT_EQ("SlowEcho", methods[1]); EXPECT_EQ("AsyncEcho", methods[2]); EXPECT_EQ("BrokenMethod", methods[3]); std::vector objects = properties->objects.value(); ASSERT_EQ(1U, objects.size()); EXPECT_EQ(ObjectPath("/TestObjectPath"), objects[0]); } TEST_F(ObjectManagerTest, UnknownObjectProxy) { ObjectProxy* object_proxy = object_manager_->GetObjectProxy( ObjectPath("/org/chromium/UnknownObject")); EXPECT_TRUE(object_proxy == NULL); } TEST_F(ObjectManagerTest, UnknownObjectProperties) { Properties* properties = static_cast( object_manager_->GetProperties(ObjectPath("/org/chromium/UnknownObject"), "org.chromium.TestInterface")); EXPECT_TRUE(properties == NULL); } TEST_F(ObjectManagerTest, UnknownInterfaceProperties) { Properties* properties = static_cast( object_manager_->GetProperties(ObjectPath("/org/chromium/TestObject"), "org.chromium.UnknownService")); EXPECT_TRUE(properties == NULL); } TEST_F(ObjectManagerTest, GetObjects) { std::vector object_paths = object_manager_->GetObjects(); ASSERT_EQ(1U, object_paths.size()); EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]); } TEST_F(ObjectManagerTest, GetObjectsWithInterface) { std::vector object_paths = object_manager_->GetObjectsWithInterface("org.chromium.TestInterface"); ASSERT_EQ(1U, object_paths.size()); EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]); } TEST_F(ObjectManagerTest, GetObjectsWithUnknownInterface) { std::vector object_paths = object_manager_->GetObjectsWithInterface("org.chromium.UnknownService"); EXPECT_EQ(0U, object_paths.size()); } TEST_F(ObjectManagerTest, SameObject) { ObjectManager* object_manager = bus_->GetObjectManager( test_service_->service_name(), ObjectPath("/org/chromium/TestService")); EXPECT_EQ(object_manager_, object_manager); } TEST_F(ObjectManagerTest, DifferentObjectForService) { ObjectManager* object_manager = bus_->GetObjectManager( "org.chromium.DifferentService", ObjectPath("/org/chromium/TestService")); EXPECT_NE(object_manager_, object_manager); } TEST_F(ObjectManagerTest, DifferentObjectForPath) { ObjectManager* object_manager = bus_->GetObjectManager( test_service_->service_name(), ObjectPath("/org/chromium/DifferentService")); EXPECT_NE(object_manager_, object_manager); } TEST_F(ObjectManagerTest, SecondObject) { PerformAction("AddObject", ObjectPath("/org/chromium/SecondObject")); WaitForObject(); ObjectProxy* object_proxy = object_manager_->GetObjectProxy( ObjectPath("/org/chromium/SecondObject")); EXPECT_TRUE(object_proxy != NULL); Properties* properties = static_cast( object_manager_->GetProperties(ObjectPath("/org/chromium/SecondObject"), "org.chromium.TestInterface")); EXPECT_TRUE(properties != NULL); std::vector object_paths = object_manager_->GetObjects(); ASSERT_EQ(2U, object_paths.size()); std::sort(object_paths.begin(), object_paths.end()); EXPECT_EQ(ObjectPath("/org/chromium/SecondObject"), object_paths[0]); EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[1]); object_paths = object_manager_->GetObjectsWithInterface("org.chromium.TestInterface"); ASSERT_EQ(2U, object_paths.size()); std::sort(object_paths.begin(), object_paths.end()); EXPECT_EQ(ObjectPath("/org/chromium/SecondObject"), object_paths[0]); EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[1]); } TEST_F(ObjectManagerTest, RemoveSecondObject) { PerformAction("AddObject", ObjectPath("/org/chromium/SecondObject")); WaitForObject(); std::vector object_paths = object_manager_->GetObjects(); ASSERT_EQ(2U, object_paths.size()); PerformAction("RemoveObject", ObjectPath("/org/chromium/SecondObject")); WaitForRemoveObject(); ObjectProxy* object_proxy = object_manager_->GetObjectProxy( ObjectPath("/org/chromium/SecondObject")); EXPECT_TRUE(object_proxy == NULL); Properties* properties = static_cast( object_manager_->GetProperties(ObjectPath("/org/chromium/SecondObject"), "org.chromium.TestInterface")); EXPECT_TRUE(properties == NULL); object_paths = object_manager_->GetObjects(); ASSERT_EQ(1U, object_paths.size()); EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]); object_paths = object_manager_->GetObjectsWithInterface("org.chromium.TestInterface"); ASSERT_EQ(1U, object_paths.size()); EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]); } TEST_F(ObjectManagerTest, OwnershipLost) { PerformAction("ReleaseOwnership", ObjectPath("/org/chromium/TestService")); WaitForRemoveObject(); std::vector object_paths = object_manager_->GetObjects(); ASSERT_EQ(0U, object_paths.size()); } TEST_F(ObjectManagerTest, OwnershipLostAndRegained) { PerformAction("Ownership", ObjectPath("/org/chromium/TestService")); WaitForRemoveObject(); WaitForObject(); std::vector object_paths = object_manager_->GetObjects(); ASSERT_EQ(1U, object_paths.size()); } TEST_F(ObjectManagerTest, PropertiesChangedAsObjectsReceived) { // Remove the existing object manager. object_manager_->UnregisterInterface("org.chromium.TestInterface"); run_loop_.reset(new base::RunLoop); EXPECT_TRUE(bus_->RemoveObjectManager( test_service_->service_name(), ObjectPath("/org/chromium/TestService"), run_loop_->QuitClosure())); run_loop_->Run(); PerformAction("SetSendImmediatePropertiesChanged", ObjectPath("/org/chromium/TestService")); object_manager_ = bus_->GetObjectManager( test_service_->service_name(), ObjectPath("/org/chromium/TestService")); object_manager_->RegisterInterface("org.chromium.TestInterface", this); // The newly created object manager should call GetManagedObjects immediately // after setting up the match rule for PropertiesChanged. We should process // the PropertiesChanged event right after that. If we don't receive it within // 2 seconds, then fail the test. message_loop_.PostDelayedTask( FROM_HERE, base::Bind(&ObjectManagerTest::PropertiesChangedTestTimeout, base::Unretained(this)), base::TimeDelta::FromSeconds(2)); while (last_name_value_ != "ChangedTestServiceName" && !timeout_expired_) { run_loop_.reset(new base::RunLoop); run_loop_->Run(); } } } // namespace dbus