1// Copyright (c) 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "dbus/object_manager.h"
6
7#include <string>
8#include <vector>
9
10#include "base/basictypes.h"
11#include "base/bind.h"
12#include "base/message_loop/message_loop.h"
13#include "base/run_loop.h"
14#include "base/threading/thread.h"
15#include "base/threading/thread_restrictions.h"
16#include "dbus/bus.h"
17#include "dbus/object_path.h"
18#include "dbus/object_proxy.h"
19#include "dbus/property.h"
20#include "dbus/test_service.h"
21#include "testing/gtest/include/gtest/gtest.h"
22
23namespace dbus {
24
25// The object manager test exercises the asynchronous APIs in ObjectManager,
26// and by extension PropertySet and Property<>.
27class ObjectManagerTest
28    : public testing::Test,
29      public ObjectManager::Interface {
30 public:
31  ObjectManagerTest() : timeout_expired_(false) {
32  }
33
34  struct Properties : public PropertySet {
35    Property<std::string> name;
36    Property<int16> version;
37    Property<std::vector<std::string> > methods;
38    Property<std::vector<ObjectPath> > objects;
39
40    Properties(ObjectProxy* object_proxy,
41               const std::string& interface_name,
42               PropertyChangedCallback property_changed_callback)
43        : PropertySet(object_proxy, interface_name, property_changed_callback) {
44      RegisterProperty("Name", &name);
45      RegisterProperty("Version", &version);
46      RegisterProperty("Methods", &methods);
47      RegisterProperty("Objects", &objects);
48    }
49  };
50
51  virtual PropertySet* CreateProperties(
52      ObjectProxy* object_proxy,
53      const ObjectPath& object_path,
54      const std::string& interface_name) OVERRIDE {
55    Properties* properties = new Properties(
56        object_proxy, interface_name,
57        base::Bind(&ObjectManagerTest::OnPropertyChanged,
58                   base::Unretained(this), object_path));
59    return static_cast<PropertySet*>(properties);
60  }
61
62  virtual void SetUp() {
63    // Make the main thread not to allow IO.
64    base::ThreadRestrictions::SetIOAllowed(false);
65
66    // Start the D-Bus thread.
67    dbus_thread_.reset(new base::Thread("D-Bus Thread"));
68    base::Thread::Options thread_options;
69    thread_options.message_loop_type = base::MessageLoop::TYPE_IO;
70    ASSERT_TRUE(dbus_thread_->StartWithOptions(thread_options));
71
72    // Start the test service, using the D-Bus thread.
73    TestService::Options options;
74    options.dbus_task_runner = dbus_thread_->message_loop_proxy();
75    test_service_.reset(new TestService(options));
76    ASSERT_TRUE(test_service_->StartService());
77    ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted());
78    ASSERT_TRUE(test_service_->HasDBusThread());
79
80    // Create the client, using the D-Bus thread.
81    Bus::Options bus_options;
82    bus_options.bus_type = Bus::SESSION;
83    bus_options.connection_type = Bus::PRIVATE;
84    bus_options.dbus_task_runner = dbus_thread_->message_loop_proxy();
85    bus_ = new Bus(bus_options);
86    ASSERT_TRUE(bus_->HasDBusThread());
87
88    object_manager_ = bus_->GetObjectManager(
89        "org.chromium.TestService",
90        ObjectPath("/org/chromium/TestService"));
91    object_manager_->RegisterInterface("org.chromium.TestInterface", this);
92
93    WaitForObject();
94  }
95
96  virtual void TearDown() {
97    bus_->ShutdownOnDBusThreadAndBlock();
98
99    // Shut down the service.
100    test_service_->ShutdownAndBlock();
101
102    // Reset to the default.
103    base::ThreadRestrictions::SetIOAllowed(true);
104
105    // Stopping a thread is considered an IO operation, so do this after
106    // allowing IO.
107    test_service_->Stop();
108
109    base::RunLoop().RunUntilIdle();
110  }
111
112  void MethodCallback(Response* response) {
113    method_callback_called_ = true;
114    run_loop_->Quit();
115  }
116
117  // Called from the PropertiesChangedAsObjectsReceived test case. The test will
118  // not run the message loop if it receives the expected PropertiesChanged
119  // signal before the timeout. This method immediately fails the test.
120  void PropertiesChangedTestTimeout() {
121    timeout_expired_ = true;
122    run_loop_->Quit();
123
124    FAIL() << "Never received PropertiesChanged";
125  }
126
127 protected:
128  // Called when an object is added.
129  virtual void ObjectAdded(const ObjectPath& object_path,
130                           const std::string& interface_name) OVERRIDE {
131    added_objects_.push_back(std::make_pair(object_path, interface_name));
132    run_loop_->Quit();
133  }
134
135  // Called when an object is removed.
136  virtual void ObjectRemoved(const ObjectPath& object_path,
137                             const std::string& interface_name) OVERRIDE {
138    removed_objects_.push_back(std::make_pair(object_path, interface_name));
139    run_loop_->Quit();
140  }
141
142  // Called when a property value is updated.
143  void OnPropertyChanged(const ObjectPath& object_path,
144                         const std::string& name) {
145    // Store the value of the "Name" property if that's the one that
146    // changed.
147    Properties* properties = static_cast<Properties*>(
148        object_manager_->GetProperties(
149            object_path,
150            "org.chromium.TestInterface"));
151    if (name == properties->name.name())
152      last_name_value_ = properties->name.value();
153
154    // Store the updated property.
155    updated_properties_.push_back(name);
156    run_loop_->Quit();
157  }
158
159  static const size_t kExpectedObjects = 1;
160  static const size_t kExpectedProperties = 4;
161
162  void WaitForObject() {
163    while (added_objects_.size() < kExpectedObjects ||
164           updated_properties_.size() < kExpectedProperties) {
165      run_loop_.reset(new base::RunLoop);
166      run_loop_->Run();
167    }
168    for (size_t i = 0; i < kExpectedObjects; ++i)
169      added_objects_.erase(added_objects_.begin());
170    for (size_t i = 0; i < kExpectedProperties; ++i)
171      updated_properties_.erase(updated_properties_.begin());
172  }
173
174  void WaitForRemoveObject() {
175    while (removed_objects_.size() < kExpectedObjects) {
176      run_loop_.reset(new base::RunLoop);
177      run_loop_->Run();
178    }
179    for (size_t i = 0; i < kExpectedObjects; ++i)
180      removed_objects_.erase(removed_objects_.begin());
181  }
182
183  void WaitForMethodCallback() {
184    run_loop_.reset(new base::RunLoop);
185    run_loop_->Run();
186    method_callback_called_ = false;
187  }
188
189  void PerformAction(const std::string& action, const ObjectPath& object_path) {
190    ObjectProxy* object_proxy = bus_->GetObjectProxy(
191        "org.chromium.TestService",
192        ObjectPath("/org/chromium/TestObject"));
193
194    MethodCall method_call("org.chromium.TestInterface", "PerformAction");
195    MessageWriter writer(&method_call);
196    writer.AppendString(action);
197    writer.AppendObjectPath(object_path);
198
199    object_proxy->CallMethod(&method_call,
200                             ObjectProxy::TIMEOUT_USE_DEFAULT,
201                             base::Bind(&ObjectManagerTest::MethodCallback,
202                                        base::Unretained(this)));
203    WaitForMethodCallback();
204  }
205
206  base::MessageLoop message_loop_;
207  scoped_ptr<base::RunLoop> run_loop_;
208  scoped_ptr<base::Thread> dbus_thread_;
209  scoped_refptr<Bus> bus_;
210  ObjectManager* object_manager_;
211  scoped_ptr<TestService> test_service_;
212
213  std::string last_name_value_;
214  bool timeout_expired_;
215
216  std::vector<std::pair<ObjectPath, std::string> > added_objects_;
217  std::vector<std::pair<ObjectPath, std::string> > removed_objects_;
218  std::vector<std::string> updated_properties_;
219
220  bool method_callback_called_;
221};
222
223
224TEST_F(ObjectManagerTest, InitialObject) {
225  ObjectProxy* object_proxy = object_manager_->GetObjectProxy(
226      ObjectPath("/org/chromium/TestObject"));
227  EXPECT_TRUE(object_proxy != NULL);
228
229  Properties* properties = static_cast<Properties*>(
230      object_manager_->GetProperties(ObjectPath("/org/chromium/TestObject"),
231                                     "org.chromium.TestInterface"));
232  EXPECT_TRUE(properties != NULL);
233
234  EXPECT_EQ("TestService", properties->name.value());
235  EXPECT_EQ(10, properties->version.value());
236
237  std::vector<std::string> methods = properties->methods.value();
238  ASSERT_EQ(4U, methods.size());
239  EXPECT_EQ("Echo", methods[0]);
240  EXPECT_EQ("SlowEcho", methods[1]);
241  EXPECT_EQ("AsyncEcho", methods[2]);
242  EXPECT_EQ("BrokenMethod", methods[3]);
243
244  std::vector<ObjectPath> objects = properties->objects.value();
245  ASSERT_EQ(1U, objects.size());
246  EXPECT_EQ(ObjectPath("/TestObjectPath"), objects[0]);
247}
248
249TEST_F(ObjectManagerTest, UnknownObjectProxy) {
250  ObjectProxy* object_proxy = object_manager_->GetObjectProxy(
251      ObjectPath("/org/chromium/UnknownObject"));
252  EXPECT_TRUE(object_proxy == NULL);
253}
254
255TEST_F(ObjectManagerTest, UnknownObjectProperties) {
256  Properties* properties = static_cast<Properties*>(
257      object_manager_->GetProperties(ObjectPath("/org/chromium/UnknownObject"),
258                                     "org.chromium.TestInterface"));
259  EXPECT_TRUE(properties == NULL);
260}
261
262TEST_F(ObjectManagerTest, UnknownInterfaceProperties) {
263  Properties* properties = static_cast<Properties*>(
264      object_manager_->GetProperties(ObjectPath("/org/chromium/TestObject"),
265                                     "org.chromium.UnknownService"));
266  EXPECT_TRUE(properties == NULL);
267}
268
269TEST_F(ObjectManagerTest, GetObjects) {
270  std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
271  ASSERT_EQ(1U, object_paths.size());
272  EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]);
273}
274
275TEST_F(ObjectManagerTest, GetObjectsWithInterface) {
276  std::vector<ObjectPath> object_paths =
277      object_manager_->GetObjectsWithInterface("org.chromium.TestInterface");
278  ASSERT_EQ(1U, object_paths.size());
279  EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]);
280}
281
282TEST_F(ObjectManagerTest, GetObjectsWithUnknownInterface) {
283  std::vector<ObjectPath> object_paths =
284      object_manager_->GetObjectsWithInterface("org.chromium.UnknownService");
285  EXPECT_EQ(0U, object_paths.size());
286}
287
288TEST_F(ObjectManagerTest, SameObject) {
289  ObjectManager* object_manager = bus_->GetObjectManager(
290      "org.chromium.TestService",
291      ObjectPath("/org/chromium/TestService"));
292  EXPECT_EQ(object_manager_, object_manager);
293}
294
295TEST_F(ObjectManagerTest, DifferentObjectForService) {
296  ObjectManager* object_manager = bus_->GetObjectManager(
297      "org.chromium.DifferentService",
298      ObjectPath("/org/chromium/TestService"));
299  EXPECT_NE(object_manager_, object_manager);
300}
301
302TEST_F(ObjectManagerTest, DifferentObjectForPath) {
303  ObjectManager* object_manager = bus_->GetObjectManager(
304      "org.chromium.TestService",
305      ObjectPath("/org/chromium/DifferentService"));
306  EXPECT_NE(object_manager_, object_manager);
307}
308
309TEST_F(ObjectManagerTest, SecondObject) {
310  PerformAction("AddObject", ObjectPath("/org/chromium/SecondObject"));
311  WaitForObject();
312
313  ObjectProxy* object_proxy = object_manager_->GetObjectProxy(
314      ObjectPath("/org/chromium/SecondObject"));
315  EXPECT_TRUE(object_proxy != NULL);
316
317  Properties* properties = static_cast<Properties*>(
318      object_manager_->GetProperties(ObjectPath("/org/chromium/SecondObject"),
319                                     "org.chromium.TestInterface"));
320  EXPECT_TRUE(properties != NULL);
321
322  std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
323  ASSERT_EQ(2U, object_paths.size());
324
325  std::sort(object_paths.begin(), object_paths.end());
326  EXPECT_EQ(ObjectPath("/org/chromium/SecondObject"), object_paths[0]);
327  EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[1]);
328
329  object_paths =
330      object_manager_->GetObjectsWithInterface("org.chromium.TestInterface");
331  ASSERT_EQ(2U, object_paths.size());
332
333  std::sort(object_paths.begin(), object_paths.end());
334  EXPECT_EQ(ObjectPath("/org/chromium/SecondObject"), object_paths[0]);
335  EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[1]);
336}
337
338TEST_F(ObjectManagerTest, RemoveSecondObject) {
339  PerformAction("AddObject", ObjectPath("/org/chromium/SecondObject"));
340  WaitForObject();
341
342  std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
343  ASSERT_EQ(2U, object_paths.size());
344
345  PerformAction("RemoveObject", ObjectPath("/org/chromium/SecondObject"));
346  WaitForRemoveObject();
347
348  ObjectProxy* object_proxy = object_manager_->GetObjectProxy(
349      ObjectPath("/org/chromium/SecondObject"));
350  EXPECT_TRUE(object_proxy == NULL);
351
352  Properties* properties = static_cast<Properties*>(
353      object_manager_->GetProperties(ObjectPath("/org/chromium/SecondObject"),
354                                     "org.chromium.TestInterface"));
355  EXPECT_TRUE(properties == NULL);
356
357  object_paths = object_manager_->GetObjects();
358  ASSERT_EQ(1U, object_paths.size());
359  EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]);
360
361  object_paths =
362      object_manager_->GetObjectsWithInterface("org.chromium.TestInterface");
363  ASSERT_EQ(1U, object_paths.size());
364  EXPECT_EQ(ObjectPath("/org/chromium/TestObject"), object_paths[0]);
365}
366
367TEST_F(ObjectManagerTest, OwnershipLost) {
368  PerformAction("ReleaseOwnership", ObjectPath("/org/chromium/TestService"));
369  WaitForRemoveObject();
370
371  std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
372  ASSERT_EQ(0U, object_paths.size());
373}
374
375TEST_F(ObjectManagerTest, OwnershipLostAndRegained) {
376  PerformAction("Ownership", ObjectPath("/org/chromium/TestService"));
377  WaitForRemoveObject();
378  WaitForObject();
379
380  std::vector<ObjectPath> object_paths = object_manager_->GetObjects();
381  ASSERT_EQ(1U, object_paths.size());
382}
383
384TEST_F(ObjectManagerTest, PropertiesChangedAsObjectsReceived) {
385  // Remove the existing object manager.
386  object_manager_->UnregisterInterface("org.chromium.TestInterface");
387  run_loop_.reset(new base::RunLoop);
388  EXPECT_TRUE(bus_->RemoveObjectManager(
389      "org.chromium.TestService",
390      ObjectPath("/org/chromium/TestService"),
391      run_loop_->QuitClosure()));
392  run_loop_->Run();
393
394  PerformAction("SetSendImmediatePropertiesChanged",
395                ObjectPath("/org/chromium/TestService"));
396
397  object_manager_ = bus_->GetObjectManager(
398      "org.chromium.TestService",
399      ObjectPath("/org/chromium/TestService"));
400  object_manager_->RegisterInterface("org.chromium.TestInterface", this);
401
402  // The newly created object manager should call GetManagedObjects immediately
403  // after setting up the match rule for PropertiesChanged. We should process
404  // the PropertiesChanged event right after that. If we don't receive it within
405  // 2 seconds, then fail the test.
406  message_loop_.PostDelayedTask(
407      FROM_HERE,
408      base::Bind(&ObjectManagerTest::PropertiesChangedTestTimeout,
409                 base::Unretained(this)),
410      base::TimeDelta::FromSeconds(2));
411
412  while (last_name_value_ != "ChangedTestServiceName" && !timeout_expired_) {
413    run_loop_.reset(new base::RunLoop);
414    run_loop_->Run();
415  }
416}
417
418}  // namespace dbus
419