| // 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 <stddef.h> |
| #include <stdint.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.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<std::string> name; |
| Property<int16_t> version; |
| Property<std::vector<std::string>> methods; |
| Property<std::vector<ObjectPath>> 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<PropertySet*>(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<Properties*>( |
| 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_; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| std::unique_ptr<base::Thread> dbus_thread_; |
| scoped_refptr<Bus> bus_; |
| ObjectManager* object_manager_; |
| std::unique_ptr<TestService> test_service_; |
| |
| std::string last_name_value_; |
| bool timeout_expired_; |
| |
| std::vector<std::pair<ObjectPath, std::string>> added_objects_; |
| std::vector<std::pair<ObjectPath, std::string>> removed_objects_; |
| std::vector<std::string> updated_properties_; |
| |
| bool method_callback_called_; |
| }; |
| |
| |
| TEST_F(ObjectManagerTest, InitialObject) { |
| ObjectProxy* object_proxy = object_manager_->GetObjectProxy( |
| ObjectPath("/org/chromium/TestObject")); |
| EXPECT_NE(nullptr, object_proxy); |
| |
| Properties* properties = static_cast<Properties*>( |
| object_manager_->GetProperties(ObjectPath("/org/chromium/TestObject"), |
| "org.chromium.TestInterface")); |
| EXPECT_NE(nullptr, properties); |
| |
| EXPECT_EQ("TestService", properties->name.value()); |
| EXPECT_EQ(10, properties->version.value()); |
| |
| std::vector<std::string> 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<ObjectPath> 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_EQ(nullptr, object_proxy); |
| } |
| |
| TEST_F(ObjectManagerTest, UnknownObjectProperties) { |
| Properties* properties = static_cast<Properties*>( |
| object_manager_->GetProperties(ObjectPath("/org/chromium/UnknownObject"), |
| "org.chromium.TestInterface")); |
| EXPECT_EQ(nullptr, properties); |
| } |
| |
| TEST_F(ObjectManagerTest, UnknownInterfaceProperties) { |
| Properties* properties = static_cast<Properties*>( |
| object_manager_->GetProperties(ObjectPath("/org/chromium/TestObject"), |
| "org.chromium.UnknownService")); |
| EXPECT_EQ(nullptr, properties); |
| } |
| |
| TEST_F(ObjectManagerTest, GetObjects) { |
| std::vector<ObjectPath> 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<ObjectPath> 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<ObjectPath> 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_NE(nullptr, object_proxy); |
| |
| Properties* properties = static_cast<Properties*>( |
| object_manager_->GetProperties(ObjectPath("/org/chromium/SecondObject"), |
| "org.chromium.TestInterface")); |
| EXPECT_NE(nullptr, properties); |
| |
| std::vector<ObjectPath> 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<ObjectPath> 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_EQ(nullptr, object_proxy); |
| |
| Properties* properties = static_cast<Properties*>( |
| object_manager_->GetProperties(ObjectPath("/org/chromium/SecondObject"), |
| "org.chromium.TestInterface")); |
| EXPECT_EQ(nullptr, properties); |
| |
| 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<ObjectPath> 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<ObjectPath> 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_.task_runner()->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 |