1 // Copyright (c) 2012 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 <algorithm> 6 #include <string> 7 #include <vector> 8 9 #include "base/bind.h" 10 #include "base/memory/scoped_ptr.h" 11 #include "base/message_loop/message_loop.h" 12 #include "base/stl_util.h" 13 #include "base/test/test_timeouts.h" 14 #include "base/threading/thread.h" 15 #include "base/threading/thread_restrictions.h" 16 #include "dbus/bus.h" 17 #include "dbus/message.h" 18 #include "dbus/object_path.h" 19 #include "dbus/object_proxy.h" 20 #include "dbus/test_service.h" 21 #include "testing/gtest/include/gtest/gtest.h" 22 23 namespace dbus { 24 25 namespace { 26 27 // See comments in ObjectProxy::RunResponseCallback() for why the number was 28 // chosen. 29 const int kHugePayloadSize = 64 << 20; // 64 MB 30 31 } // namespace 32 33 // The end-to-end test exercises the asynchronous APIs in ObjectProxy and 34 // ExportedObject. 35 class EndToEndAsyncTest : public testing::Test { 36 public: 37 EndToEndAsyncTest() : on_disconnected_call_count_(0) {} 38 39 virtual void SetUp() { 40 // Make the main thread not to allow IO. 41 base::ThreadRestrictions::SetIOAllowed(false); 42 43 // Start the D-Bus thread. 44 dbus_thread_.reset(new base::Thread("D-Bus Thread")); 45 base::Thread::Options thread_options; 46 thread_options.message_loop_type = base::MessageLoop::TYPE_IO; 47 ASSERT_TRUE(dbus_thread_->StartWithOptions(thread_options)); 48 49 // Start the test service, using the D-Bus thread. 50 TestService::Options options; 51 options.dbus_task_runner = dbus_thread_->message_loop_proxy(); 52 test_service_.reset(new TestService(options)); 53 ASSERT_TRUE(test_service_->StartService()); 54 ASSERT_TRUE(test_service_->WaitUntilServiceIsStarted()); 55 ASSERT_TRUE(test_service_->HasDBusThread()); 56 57 // Create the client, using the D-Bus thread. 58 Bus::Options bus_options; 59 bus_options.bus_type = Bus::SESSION; 60 bus_options.connection_type = Bus::PRIVATE; 61 bus_options.dbus_task_runner = dbus_thread_->message_loop_proxy(); 62 bus_options.disconnected_callback = 63 base::Bind(&EndToEndAsyncTest::OnDisconnected, base::Unretained(this)); 64 bus_ = new Bus(bus_options); 65 object_proxy_ = bus_->GetObjectProxy( 66 "org.chromium.TestService", 67 ObjectPath("/org/chromium/TestObject")); 68 ASSERT_TRUE(bus_->HasDBusThread()); 69 70 // Connect to the "Test" signal of "org.chromium.TestInterface" from 71 // the remote object. 72 object_proxy_->ConnectToSignal( 73 "org.chromium.TestInterface", 74 "Test", 75 base::Bind(&EndToEndAsyncTest::OnTestSignal, 76 base::Unretained(this)), 77 base::Bind(&EndToEndAsyncTest::OnConnected, 78 base::Unretained(this))); 79 // Wait until the object proxy is connected to the signal. 80 message_loop_.Run(); 81 82 // Connect to the "Test2" signal of "org.chromium.TestInterface" from 83 // the remote object. There was a bug where we were emitting error 84 // messages like "Requested to remove an unknown match rule: ..." at 85 // the shutdown of Bus when an object proxy is connected to more than 86 // one signal of the same interface. See crosbug.com/23382 for details. 87 object_proxy_->ConnectToSignal( 88 "org.chromium.TestInterface", 89 "Test2", 90 base::Bind(&EndToEndAsyncTest::OnTest2Signal, 91 base::Unretained(this)), 92 base::Bind(&EndToEndAsyncTest::OnConnected, 93 base::Unretained(this))); 94 // Wait until the object proxy is connected to the signal. 95 message_loop_.Run(); 96 97 // Create a second object proxy for the root object. 98 root_object_proxy_ = bus_->GetObjectProxy("org.chromium.TestService", 99 ObjectPath("/")); 100 ASSERT_TRUE(bus_->HasDBusThread()); 101 102 // Connect to the "Test" signal of "org.chromium.TestInterface" from 103 // the root remote object too. 104 root_object_proxy_->ConnectToSignal( 105 "org.chromium.TestInterface", 106 "Test", 107 base::Bind(&EndToEndAsyncTest::OnRootTestSignal, 108 base::Unretained(this)), 109 base::Bind(&EndToEndAsyncTest::OnConnected, 110 base::Unretained(this))); 111 // Wait until the root object proxy is connected to the signal. 112 message_loop_.Run(); 113 } 114 115 virtual void TearDown() { 116 bus_->ShutdownOnDBusThreadAndBlock(); 117 118 // Shut down the service. 119 test_service_->ShutdownAndBlock(); 120 121 // Reset to the default. 122 base::ThreadRestrictions::SetIOAllowed(true); 123 124 // Stopping a thread is considered an IO operation, so do this after 125 // allowing IO. 126 test_service_->Stop(); 127 } 128 129 protected: 130 // Replaces the bus with a broken one. 131 void SetUpBrokenBus() { 132 // Shut down the existing bus. 133 bus_->ShutdownOnDBusThreadAndBlock(); 134 135 // Create new bus with invalid address. 136 const char kInvalidAddress[] = ""; 137 Bus::Options bus_options; 138 bus_options.bus_type = Bus::CUSTOM_ADDRESS; 139 bus_options.address = kInvalidAddress; 140 bus_options.connection_type = Bus::PRIVATE; 141 bus_options.dbus_task_runner = dbus_thread_->message_loop_proxy(); 142 bus_ = new Bus(bus_options); 143 ASSERT_TRUE(bus_->HasDBusThread()); 144 145 // Create new object proxy. 146 object_proxy_ = bus_->GetObjectProxy( 147 "org.chromium.TestService", 148 ObjectPath("/org/chromium/TestObject")); 149 } 150 151 // Calls the method asynchronously. OnResponse() will be called once the 152 // response is received. 153 void CallMethod(MethodCall* method_call, 154 int timeout_ms) { 155 object_proxy_->CallMethod(method_call, 156 timeout_ms, 157 base::Bind(&EndToEndAsyncTest::OnResponse, 158 base::Unretained(this))); 159 } 160 161 // Calls the method asynchronously. OnResponse() will be called once the 162 // response is received without error, otherwise OnError() will be called. 163 void CallMethodWithErrorCallback(MethodCall* method_call, 164 int timeout_ms) { 165 object_proxy_->CallMethodWithErrorCallback( 166 method_call, 167 timeout_ms, 168 base::Bind(&EndToEndAsyncTest::OnResponse, base::Unretained(this)), 169 base::Bind(&EndToEndAsyncTest::OnError, base::Unretained(this))); 170 } 171 172 // Wait for the give number of responses. 173 void WaitForResponses(size_t num_responses) { 174 while (response_strings_.size() < num_responses) { 175 message_loop_.Run(); 176 } 177 } 178 179 // Called when the response is received. 180 void OnResponse(Response* response) { 181 // |response| will be deleted on exit of the function. Copy the 182 // payload to |response_strings_|. 183 if (response) { 184 MessageReader reader(response); 185 std::string response_string; 186 ASSERT_TRUE(reader.PopString(&response_string)); 187 response_strings_.push_back(response_string); 188 } else { 189 response_strings_.push_back(std::string()); 190 } 191 message_loop_.Quit(); 192 }; 193 194 // Wait for the given number of errors. 195 void WaitForErrors(size_t num_errors) { 196 while (error_names_.size() < num_errors) { 197 message_loop_.Run(); 198 } 199 } 200 201 // Called when an error is received. 202 void OnError(ErrorResponse* error) { 203 // |error| will be deleted on exit of the function. Copy the payload to 204 // |error_names_|. 205 if (error) { 206 ASSERT_NE("", error->GetErrorName()); 207 error_names_.push_back(error->GetErrorName()); 208 } else { 209 error_names_.push_back(std::string()); 210 } 211 message_loop_.Quit(); 212 } 213 214 // Called when the "Test" signal is received, in the main thread. 215 // Copy the string payload to |test_signal_string_|. 216 void OnTestSignal(Signal* signal) { 217 MessageReader reader(signal); 218 ASSERT_TRUE(reader.PopString(&test_signal_string_)); 219 message_loop_.Quit(); 220 } 221 222 // Called when the "Test" signal is received, in the main thread, by 223 // the root object proxy. Copy the string payload to 224 // |root_test_signal_string_|. 225 void OnRootTestSignal(Signal* signal) { 226 MessageReader reader(signal); 227 ASSERT_TRUE(reader.PopString(&root_test_signal_string_)); 228 message_loop_.Quit(); 229 } 230 231 // Called when the "Test2" signal is received, in the main thread. 232 void OnTest2Signal(Signal* signal) { 233 MessageReader reader(signal); 234 message_loop_.Quit(); 235 } 236 237 // Called when connected to the signal. 238 void OnConnected(const std::string& interface_name, 239 const std::string& signal_name, 240 bool success) { 241 ASSERT_TRUE(success); 242 message_loop_.Quit(); 243 } 244 245 // Called when the connection with dbus-daemon is disconnected. 246 void OnDisconnected() { 247 message_loop_.Quit(); 248 ++on_disconnected_call_count_; 249 } 250 251 // Wait for the hey signal to be received. 252 void WaitForTestSignal() { 253 // OnTestSignal() will quit the message loop. 254 message_loop_.Run(); 255 } 256 257 base::MessageLoop message_loop_; 258 std::vector<std::string> response_strings_; 259 std::vector<std::string> error_names_; 260 scoped_ptr<base::Thread> dbus_thread_; 261 scoped_refptr<Bus> bus_; 262 ObjectProxy* object_proxy_; 263 ObjectProxy* root_object_proxy_; 264 scoped_ptr<TestService> test_service_; 265 // Text message from "Test" signal. 266 std::string test_signal_string_; 267 // Text message from "Test" signal delivered to root. 268 std::string root_test_signal_string_; 269 int on_disconnected_call_count_; 270 }; 271 272 TEST_F(EndToEndAsyncTest, Echo) { 273 const char* kHello = "hello"; 274 275 // Create the method call. 276 MethodCall method_call("org.chromium.TestInterface", "Echo"); 277 MessageWriter writer(&method_call); 278 writer.AppendString(kHello); 279 280 // Call the method. 281 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 282 CallMethod(&method_call, timeout_ms); 283 284 // Check the response. 285 WaitForResponses(1); 286 EXPECT_EQ(kHello, response_strings_[0]); 287 } 288 289 TEST_F(EndToEndAsyncTest, EchoWithErrorCallback) { 290 const char* kHello = "hello"; 291 292 // Create the method call. 293 MethodCall method_call("org.chromium.TestInterface", "Echo"); 294 MessageWriter writer(&method_call); 295 writer.AppendString(kHello); 296 297 // Call the method. 298 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 299 CallMethodWithErrorCallback(&method_call, timeout_ms); 300 301 // Check the response. 302 WaitForResponses(1); 303 EXPECT_EQ(kHello, response_strings_[0]); 304 EXPECT_TRUE(error_names_.empty()); 305 } 306 307 // Call Echo method three times. 308 TEST_F(EndToEndAsyncTest, EchoThreeTimes) { 309 const char* kMessages[] = { "foo", "bar", "baz" }; 310 311 for (size_t i = 0; i < arraysize(kMessages); ++i) { 312 // Create the method call. 313 MethodCall method_call("org.chromium.TestInterface", "Echo"); 314 MessageWriter writer(&method_call); 315 writer.AppendString(kMessages[i]); 316 317 // Call the method. 318 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 319 CallMethod(&method_call, timeout_ms); 320 } 321 322 // Check the responses. 323 WaitForResponses(3); 324 // Sort as the order of the returned messages is not deterministic. 325 std::sort(response_strings_.begin(), response_strings_.end()); 326 EXPECT_EQ("bar", response_strings_[0]); 327 EXPECT_EQ("baz", response_strings_[1]); 328 EXPECT_EQ("foo", response_strings_[2]); 329 } 330 331 TEST_F(EndToEndAsyncTest, Echo_HugePayload) { 332 const std::string kHugePayload(kHugePayloadSize, 'o'); 333 334 // Create the method call with a huge payload. 335 MethodCall method_call("org.chromium.TestInterface", "Echo"); 336 MessageWriter writer(&method_call); 337 writer.AppendString(kHugePayload); 338 339 // Call the method. 340 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 341 CallMethod(&method_call, timeout_ms); 342 343 // This caused a DCHECK failure before. Ensure that the issue is fixed. 344 WaitForResponses(1); 345 EXPECT_EQ(kHugePayload, response_strings_[0]); 346 } 347 348 TEST_F(EndToEndAsyncTest, BrokenBus) { 349 const char* kHello = "hello"; 350 351 // Set up a broken bus. 352 SetUpBrokenBus(); 353 354 // Create the method call. 355 MethodCall method_call("org.chromium.TestInterface", "Echo"); 356 MessageWriter writer(&method_call); 357 writer.AppendString(kHello); 358 359 // Call the method. 360 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 361 CallMethod(&method_call, timeout_ms); 362 WaitForResponses(1); 363 364 // Should fail because of the broken bus. 365 ASSERT_EQ("", response_strings_[0]); 366 } 367 368 TEST_F(EndToEndAsyncTest, BrokenBusWithErrorCallback) { 369 const char* kHello = "hello"; 370 371 // Set up a broken bus. 372 SetUpBrokenBus(); 373 374 // Create the method call. 375 MethodCall method_call("org.chromium.TestInterface", "Echo"); 376 MessageWriter writer(&method_call); 377 writer.AppendString(kHello); 378 379 // Call the method. 380 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 381 CallMethodWithErrorCallback(&method_call, timeout_ms); 382 WaitForErrors(1); 383 384 // Should fail because of the broken bus. 385 ASSERT_TRUE(response_strings_.empty()); 386 ASSERT_EQ("", error_names_[0]); 387 } 388 389 TEST_F(EndToEndAsyncTest, Timeout) { 390 const char* kHello = "hello"; 391 392 // Create the method call. 393 MethodCall method_call("org.chromium.TestInterface", "SlowEcho"); 394 MessageWriter writer(&method_call); 395 writer.AppendString(kHello); 396 397 // Call the method with timeout of 0ms. 398 const int timeout_ms = 0; 399 CallMethod(&method_call, timeout_ms); 400 WaitForResponses(1); 401 402 // Should fail because of timeout. 403 ASSERT_EQ("", response_strings_[0]); 404 } 405 406 TEST_F(EndToEndAsyncTest, TimeoutWithErrorCallback) { 407 const char* kHello = "hello"; 408 409 // Create the method call. 410 MethodCall method_call("org.chromium.TestInterface", "SlowEcho"); 411 MessageWriter writer(&method_call); 412 writer.AppendString(kHello); 413 414 // Call the method with timeout of 0ms. 415 const int timeout_ms = 0; 416 CallMethodWithErrorCallback(&method_call, timeout_ms); 417 WaitForErrors(1); 418 419 // Should fail because of timeout. 420 ASSERT_TRUE(response_strings_.empty()); 421 ASSERT_EQ(DBUS_ERROR_NO_REPLY, error_names_[0]); 422 } 423 424 // Tests calling a method that sends its reply asynchronously. 425 TEST_F(EndToEndAsyncTest, AsyncEcho) { 426 const char* kHello = "hello"; 427 428 // Create the method call. 429 MethodCall method_call("org.chromium.TestInterface", "AsyncEcho"); 430 MessageWriter writer(&method_call); 431 writer.AppendString(kHello); 432 433 // Call the method. 434 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 435 CallMethod(&method_call, timeout_ms); 436 437 // Check the response. 438 WaitForResponses(1); 439 EXPECT_EQ(kHello, response_strings_[0]); 440 } 441 442 TEST_F(EndToEndAsyncTest, NonexistentMethod) { 443 MethodCall method_call("org.chromium.TestInterface", "Nonexistent"); 444 445 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 446 CallMethod(&method_call, timeout_ms); 447 WaitForResponses(1); 448 449 // Should fail because the method is nonexistent. 450 ASSERT_EQ("", response_strings_[0]); 451 } 452 453 TEST_F(EndToEndAsyncTest, NonexistentMethodWithErrorCallback) { 454 MethodCall method_call("org.chromium.TestInterface", "Nonexistent"); 455 456 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 457 CallMethodWithErrorCallback(&method_call, timeout_ms); 458 WaitForErrors(1); 459 460 // Should fail because the method is nonexistent. 461 ASSERT_TRUE(response_strings_.empty()); 462 ASSERT_EQ(DBUS_ERROR_UNKNOWN_METHOD, error_names_[0]); 463 } 464 465 TEST_F(EndToEndAsyncTest, BrokenMethod) { 466 MethodCall method_call("org.chromium.TestInterface", "BrokenMethod"); 467 468 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 469 CallMethod(&method_call, timeout_ms); 470 WaitForResponses(1); 471 472 // Should fail because the method is broken. 473 ASSERT_EQ("", response_strings_[0]); 474 } 475 476 TEST_F(EndToEndAsyncTest, BrokenMethodWithErrorCallback) { 477 MethodCall method_call("org.chromium.TestInterface", "BrokenMethod"); 478 479 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 480 CallMethodWithErrorCallback(&method_call, timeout_ms); 481 WaitForErrors(1); 482 483 // Should fail because the method is broken. 484 ASSERT_TRUE(response_strings_.empty()); 485 ASSERT_EQ(DBUS_ERROR_FAILED, error_names_[0]); 486 } 487 488 TEST_F(EndToEndAsyncTest, InvalidObjectPath) { 489 // Trailing '/' is only allowed for the root path. 490 const ObjectPath invalid_object_path("/org/chromium/TestObject/"); 491 492 // Replace object proxy with new one. 493 object_proxy_ = bus_->GetObjectProxy("org.chromium.TestService", 494 invalid_object_path); 495 496 MethodCall method_call("org.chromium.TestInterface", "Echo"); 497 498 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 499 CallMethodWithErrorCallback(&method_call, timeout_ms); 500 WaitForErrors(1); 501 502 // Should fail because of the invalid path. 503 ASSERT_TRUE(response_strings_.empty()); 504 ASSERT_EQ("", error_names_[0]); 505 } 506 507 TEST_F(EndToEndAsyncTest, InvalidServiceName) { 508 // Bus name cannot contain '/'. 509 const std::string invalid_service_name = ":1/2"; 510 511 // Replace object proxy with new one. 512 object_proxy_ = bus_->GetObjectProxy( 513 invalid_service_name, ObjectPath("org.chromium.TestObject")); 514 515 MethodCall method_call("org.chromium.TestInterface", "Echo"); 516 517 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 518 CallMethodWithErrorCallback(&method_call, timeout_ms); 519 WaitForErrors(1); 520 521 // Should fail because of the invalid bus name. 522 ASSERT_TRUE(response_strings_.empty()); 523 ASSERT_EQ("", error_names_[0]); 524 } 525 526 TEST_F(EndToEndAsyncTest, EmptyResponseCallback) { 527 const char* kHello = "hello"; 528 529 // Create the method call. 530 MethodCall method_call("org.chromium.TestInterface", "Echo"); 531 MessageWriter writer(&method_call); 532 writer.AppendString(kHello); 533 534 // Call the method with an empty callback. 535 const int timeout_ms = ObjectProxy::TIMEOUT_USE_DEFAULT; 536 object_proxy_->CallMethod(&method_call, 537 timeout_ms, 538 ObjectProxy::EmptyResponseCallback()); 539 // Post a delayed task to quit the message loop. 540 message_loop_.PostDelayedTask(FROM_HERE, 541 base::MessageLoop::QuitClosure(), 542 TestTimeouts::tiny_timeout()); 543 message_loop_.Run(); 544 // We cannot tell if the empty callback is called, but at least we can 545 // check if the test does not crash. 546 } 547 548 TEST_F(EndToEndAsyncTest, TestSignal) { 549 const char kMessage[] = "hello, world"; 550 // Send the test signal from the exported object. 551 test_service_->SendTestSignal(kMessage); 552 // Receive the signal with the object proxy. The signal is handled in 553 // EndToEndAsyncTest::OnTestSignal() in the main thread. 554 WaitForTestSignal(); 555 ASSERT_EQ(kMessage, test_signal_string_); 556 } 557 558 TEST_F(EndToEndAsyncTest, TestSignalFromRoot) { 559 const char kMessage[] = "hello, world"; 560 // Object proxies are tied to a particular object path, if a signal 561 // arrives from a different object path like "/" the first object proxy 562 // |object_proxy_| should not handle it, and should leave it for the root 563 // object proxy |root_object_proxy_|. 564 test_service_->SendTestSignalFromRoot(kMessage); 565 WaitForTestSignal(); 566 // Verify the signal was not received by the specific proxy. 567 ASSERT_TRUE(test_signal_string_.empty()); 568 // Verify the string WAS received by the root proxy. 569 ASSERT_EQ(kMessage, root_test_signal_string_); 570 } 571 572 TEST_F(EndToEndAsyncTest, TestHugeSignal) { 573 const std::string kHugeMessage(kHugePayloadSize, 'o'); 574 575 // Send the huge signal from the exported object. 576 test_service_->SendTestSignal(kHugeMessage); 577 // This caused a DCHECK failure before. Ensure that the issue is fixed. 578 WaitForTestSignal(); 579 ASSERT_EQ(kHugeMessage, test_signal_string_); 580 } 581 582 TEST_F(EndToEndAsyncTest, DisconnectedSignal) { 583 bus_->GetDBusTaskRunner()->PostTask(FROM_HERE, 584 base::Bind(&Bus::ClosePrivateConnection, 585 base::Unretained(bus_.get()))); 586 // OnDisconnected callback quits message loop. 587 message_loop_.Run(); 588 EXPECT_EQ(1, on_disconnected_call_count_); 589 } 590 591 class SignalMultipleHandlerTest : public EndToEndAsyncTest { 592 public: 593 SignalMultipleHandlerTest() { 594 } 595 596 virtual void SetUp() { 597 // Set up base class. 598 EndToEndAsyncTest::SetUp(); 599 600 // Connect the root object proxy's signal handler to a new handler 601 // so that we can verify that a second call to ConnectSignal() delivers 602 // to both our new handler and the old. 603 object_proxy_->ConnectToSignal( 604 "org.chromium.TestInterface", 605 "Test", 606 base::Bind(&SignalMultipleHandlerTest::OnAdditionalTestSignal, 607 base::Unretained(this)), 608 base::Bind(&SignalMultipleHandlerTest::OnAdditionalConnected, 609 base::Unretained(this))); 610 // Wait until the object proxy is connected to the signal. 611 message_loop_.Run(); 612 } 613 614 protected: 615 // Called when the "Test" signal is received, in the main thread. 616 // Copy the string payload to |additional_test_signal_string_|. 617 void OnAdditionalTestSignal(Signal* signal) { 618 MessageReader reader(signal); 619 ASSERT_TRUE(reader.PopString(&additional_test_signal_string_)); 620 message_loop_.Quit(); 621 } 622 623 // Called when connected to the signal. 624 void OnAdditionalConnected(const std::string& interface_name, 625 const std::string& signal_name, 626 bool success) { 627 ASSERT_TRUE(success); 628 message_loop_.Quit(); 629 } 630 631 // Text message from "Test" signal delivered to additional handler. 632 std::string additional_test_signal_string_; 633 }; 634 635 TEST_F(SignalMultipleHandlerTest, TestMultipleHandlers) { 636 const char kMessage[] = "hello, world"; 637 // Send the test signal from the exported object. 638 test_service_->SendTestSignal(kMessage); 639 // Receive the signal with the object proxy. 640 WaitForTestSignal(); 641 // Verify the string WAS received by the original handler. 642 ASSERT_EQ(kMessage, test_signal_string_); 643 // Verify the signal WAS ALSO received by the additional handler. 644 ASSERT_EQ(kMessage, additional_test_signal_string_); 645 } 646 647 } // namespace dbus 648