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 <string> 6 7 #include "base/bind.h" 8 #include "base/callback.h" 9 #include "base/compiler_specific.h" 10 #include "base/files/file_path.h" 11 #include "base/memory/scoped_ptr.h" 12 #include "base/message_loop/message_loop.h" 13 #include "base/run_loop.h" 14 #include "base/synchronization/lock.h" 15 #include "base/threading/thread.h" 16 #include "base/values.h" 17 #include "chrome/test/chromedriver/chrome/status.h" 18 #include "chrome/test/chromedriver/chrome/stub_chrome.h" 19 #include "chrome/test/chromedriver/chrome/stub_web_view.h" 20 #include "chrome/test/chromedriver/chrome/web_view.h" 21 #include "chrome/test/chromedriver/command_listener_proxy.h" 22 #include "chrome/test/chromedriver/commands.h" 23 #include "chrome/test/chromedriver/element_commands.h" 24 #include "chrome/test/chromedriver/session.h" 25 #include "chrome/test/chromedriver/session_commands.h" 26 #include "chrome/test/chromedriver/window_commands.h" 27 #include "testing/gtest/include/gtest/gtest.h" 28 #include "third_party/webdriver/atoms.h" 29 30 namespace { 31 32 void OnGetStatus(const Status& status, 33 scoped_ptr<base::Value> value, 34 const std::string& session_id) { 35 ASSERT_EQ(kOk, status.code()); 36 base::DictionaryValue* dict; 37 ASSERT_TRUE(value->GetAsDictionary(&dict)); 38 base::Value* unused; 39 ASSERT_TRUE(dict->Get("os.name", &unused)); 40 ASSERT_TRUE(dict->Get("os.version", &unused)); 41 ASSERT_TRUE(dict->Get("os.arch", &unused)); 42 ASSERT_TRUE(dict->Get("build.version", &unused)); 43 } 44 45 } // namespace 46 47 TEST(CommandsTest, GetStatus) { 48 base::DictionaryValue params; 49 ExecuteGetStatus(params, std::string(), base::Bind(&OnGetStatus)); 50 } 51 52 namespace { 53 54 void ExecuteStubQuit( 55 int* count, 56 const base::DictionaryValue& params, 57 const std::string& session_id, 58 const CommandCallback& callback) { 59 if (*count == 0) { 60 EXPECT_STREQ("id", session_id.c_str()); 61 } else { 62 EXPECT_STREQ("id2", session_id.c_str()); 63 } 64 (*count)++; 65 callback.Run(Status(kOk), scoped_ptr<base::Value>(), session_id); 66 } 67 68 void OnQuitAll(const Status& status, 69 scoped_ptr<base::Value> value, 70 const std::string& session_id) { 71 ASSERT_EQ(kOk, status.code()); 72 ASSERT_FALSE(value.get()); 73 } 74 75 } // namespace 76 77 TEST(CommandsTest, QuitAll) { 78 SessionThreadMap map; 79 Session session("id"); 80 Session session2("id2"); 81 map[session.id] = make_linked_ptr(new base::Thread("1")); 82 map[session2.id] = make_linked_ptr(new base::Thread("2")); 83 84 int count = 0; 85 Command cmd = base::Bind(&ExecuteStubQuit, &count); 86 base::DictionaryValue params; 87 base::MessageLoop loop; 88 ExecuteQuitAll(cmd, &map, params, std::string(), base::Bind(&OnQuitAll)); 89 ASSERT_EQ(2, count); 90 } 91 92 namespace { 93 94 Status ExecuteSimpleCommand( 95 const std::string& expected_id, 96 base::DictionaryValue* expected_params, 97 base::Value* value, 98 Session* session, 99 const base::DictionaryValue& params, 100 scoped_ptr<base::Value>* return_value) { 101 EXPECT_EQ(expected_id, session->id); 102 EXPECT_TRUE(expected_params->Equals(¶ms)); 103 return_value->reset(value->DeepCopy()); 104 session->quit = true; 105 return Status(kOk); 106 } 107 108 void OnSimpleCommand(base::RunLoop* run_loop, 109 const std::string& expected_session_id, 110 base::Value* expected_value, 111 const Status& status, 112 scoped_ptr<base::Value> value, 113 const std::string& session_id) { 114 ASSERT_EQ(kOk, status.code()); 115 ASSERT_TRUE(expected_value->Equals(value.get())); 116 ASSERT_EQ(expected_session_id, session_id); 117 run_loop->Quit(); 118 } 119 120 } // namespace 121 122 TEST(CommandsTest, ExecuteSessionCommand) { 123 SessionThreadMap map; 124 linked_ptr<base::Thread> thread(new base::Thread("1")); 125 ASSERT_TRUE(thread->Start()); 126 std::string id("id"); 127 thread->message_loop()->PostTask( 128 FROM_HERE, 129 base::Bind(&internal::CreateSessionOnSessionThreadForTesting, id)); 130 map[id] = thread; 131 132 base::DictionaryValue params; 133 params.SetInteger("param", 5); 134 base::FundamentalValue expected_value(6); 135 SessionCommand cmd = base::Bind( 136 &ExecuteSimpleCommand, id, ¶ms, &expected_value); 137 138 base::MessageLoop loop; 139 base::RunLoop run_loop; 140 ExecuteSessionCommand( 141 &map, 142 "cmd", 143 cmd, 144 false, 145 params, 146 id, 147 base::Bind(&OnSimpleCommand, &run_loop, id, &expected_value)); 148 run_loop.Run(); 149 } 150 151 namespace { 152 153 Status ShouldNotBeCalled( 154 Session* session, 155 const base::DictionaryValue& params, 156 scoped_ptr<base::Value>* value) { 157 EXPECT_TRUE(false); 158 return Status(kOk); 159 } 160 161 void OnNoSuchSession(const Status& status, 162 scoped_ptr<base::Value> value, 163 const std::string& session_id) { 164 EXPECT_EQ(kNoSuchSession, status.code()); 165 EXPECT_FALSE(value.get()); 166 } 167 168 void OnNoSuchSessionIsOk(const Status& status, 169 scoped_ptr<base::Value> value, 170 const std::string& session_id) { 171 EXPECT_EQ(kOk, status.code()); 172 EXPECT_FALSE(value.get()); 173 } 174 175 } // namespace 176 177 TEST(CommandsTest, ExecuteSessionCommandOnNoSuchSession) { 178 SessionThreadMap map; 179 base::DictionaryValue params; 180 ExecuteSessionCommand(&map, 181 "cmd", 182 base::Bind(&ShouldNotBeCalled), 183 false, 184 params, 185 "session", 186 base::Bind(&OnNoSuchSession)); 187 } 188 189 TEST(CommandsTest, ExecuteSessionCommandOnNoSuchSessionWhenItExpectsOk) { 190 SessionThreadMap map; 191 base::DictionaryValue params; 192 ExecuteSessionCommand(&map, 193 "cmd", 194 base::Bind(&ShouldNotBeCalled), 195 true, 196 params, 197 "session", 198 base::Bind(&OnNoSuchSessionIsOk)); 199 } 200 201 namespace { 202 203 void OnNoSuchSessionAndQuit(base::RunLoop* run_loop, 204 const Status& status, 205 scoped_ptr<base::Value> value, 206 const std::string& session_id) { 207 run_loop->Quit(); 208 EXPECT_EQ(kNoSuchSession, status.code()); 209 EXPECT_FALSE(value.get()); 210 } 211 212 } // namespace 213 214 TEST(CommandsTest, ExecuteSessionCommandOnJustDeletedSession) { 215 SessionThreadMap map; 216 linked_ptr<base::Thread> thread(new base::Thread("1")); 217 ASSERT_TRUE(thread->Start()); 218 std::string id("id"); 219 map[id] = thread; 220 221 base::MessageLoop loop; 222 base::RunLoop run_loop; 223 ExecuteSessionCommand(&map, 224 "cmd", 225 base::Bind(&ShouldNotBeCalled), 226 false, 227 base::DictionaryValue(), 228 "session", 229 base::Bind(&OnNoSuchSessionAndQuit, &run_loop)); 230 run_loop.Run(); 231 } 232 233 namespace { 234 235 enum TestScenario { 236 kElementExistsQueryOnce = 0, 237 kElementExistsQueryTwice, 238 kElementNotExistsQueryOnce, 239 kElementExistsTimeout 240 }; 241 242 class FindElementWebView : public StubWebView { 243 public: 244 FindElementWebView(bool only_one, TestScenario scenario) 245 : StubWebView("1"), only_one_(only_one), scenario_(scenario), 246 current_count_(0) { 247 switch (scenario_) { 248 case kElementExistsQueryOnce: 249 case kElementExistsQueryTwice: 250 case kElementExistsTimeout: { 251 if (only_one_) { 252 base::DictionaryValue element; 253 element.SetString("ELEMENT", "1"); 254 result_.reset(element.DeepCopy()); 255 } else { 256 base::DictionaryValue element1; 257 element1.SetString("ELEMENT", "1"); 258 base::DictionaryValue element2; 259 element2.SetString("ELEMENT", "2"); 260 base::ListValue list; 261 list.Append(element1.DeepCopy()); 262 list.Append(element2.DeepCopy()); 263 result_.reset(list.DeepCopy()); 264 } 265 break; 266 } 267 case kElementNotExistsQueryOnce: { 268 if (only_one_) 269 result_.reset(base::Value::CreateNullValue()); 270 else 271 result_.reset(new base::ListValue()); 272 break; 273 } 274 } 275 } 276 virtual ~FindElementWebView() {} 277 278 void Verify(const std::string& expected_frame, 279 const base::ListValue* expected_args, 280 const base::Value* actrual_result) { 281 EXPECT_EQ(expected_frame, frame_); 282 std::string function; 283 if (only_one_) 284 function = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENT); 285 else 286 function = webdriver::atoms::asString(webdriver::atoms::FIND_ELEMENTS); 287 EXPECT_EQ(function, function_); 288 ASSERT_TRUE(args_.get()); 289 EXPECT_TRUE(expected_args->Equals(args_.get())); 290 ASSERT_TRUE(actrual_result); 291 EXPECT_TRUE(result_->Equals(actrual_result)); 292 } 293 294 // Overridden from WebView: 295 virtual Status CallFunction(const std::string& frame, 296 const std::string& function, 297 const base::ListValue& args, 298 scoped_ptr<base::Value>* result) OVERRIDE { 299 ++current_count_; 300 if (scenario_ == kElementExistsTimeout || 301 (scenario_ == kElementExistsQueryTwice && current_count_ == 1)) { 302 // Always return empty result when testing timeout. 303 if (only_one_) 304 result->reset(base::Value::CreateNullValue()); 305 else 306 result->reset(new base::ListValue()); 307 } else { 308 switch (scenario_) { 309 case kElementExistsQueryOnce: 310 case kElementNotExistsQueryOnce: { 311 EXPECT_EQ(1, current_count_); 312 break; 313 } 314 case kElementExistsQueryTwice: { 315 EXPECT_EQ(2, current_count_); 316 break; 317 } 318 default: { 319 break; 320 } 321 } 322 323 result->reset(result_->DeepCopy()); 324 frame_ = frame; 325 function_ = function; 326 args_.reset(args.DeepCopy()); 327 } 328 return Status(kOk); 329 } 330 331 private: 332 bool only_one_; 333 TestScenario scenario_; 334 int current_count_; 335 std::string frame_; 336 std::string function_; 337 scoped_ptr<base::ListValue> args_; 338 scoped_ptr<base::Value> result_; 339 }; 340 341 } // namespace 342 343 TEST(CommandsTest, SuccessfulFindElement) { 344 FindElementWebView web_view(true, kElementExistsQueryTwice); 345 Session session("id"); 346 session.implicit_wait = base::TimeDelta::FromSeconds(1); 347 session.SwitchToSubFrame("frame_id1", std::string()); 348 base::DictionaryValue params; 349 params.SetString("using", "id"); 350 params.SetString("value", "a"); 351 scoped_ptr<base::Value> result; 352 ASSERT_EQ(kOk, 353 ExecuteFindElement(1, &session, &web_view, params, &result).code()); 354 base::DictionaryValue param; 355 param.SetString("id", "a"); 356 base::ListValue expected_args; 357 expected_args.Append(param.DeepCopy()); 358 web_view.Verify("frame_id1", &expected_args, result.get()); 359 } 360 361 TEST(CommandsTest, FailedFindElement) { 362 FindElementWebView web_view(true, kElementNotExistsQueryOnce); 363 Session session("id"); 364 base::DictionaryValue params; 365 params.SetString("using", "id"); 366 params.SetString("value", "a"); 367 scoped_ptr<base::Value> result; 368 ASSERT_EQ(kNoSuchElement, 369 ExecuteFindElement(1, &session, &web_view, params, &result).code()); 370 } 371 372 TEST(CommandsTest, SuccessfulFindElements) { 373 FindElementWebView web_view(false, kElementExistsQueryTwice); 374 Session session("id"); 375 session.implicit_wait = base::TimeDelta::FromSeconds(1); 376 session.SwitchToSubFrame("frame_id2", std::string()); 377 base::DictionaryValue params; 378 params.SetString("using", "name"); 379 params.SetString("value", "b"); 380 scoped_ptr<base::Value> result; 381 ASSERT_EQ( 382 kOk, 383 ExecuteFindElements(1, &session, &web_view, params, &result).code()); 384 base::DictionaryValue param; 385 param.SetString("name", "b"); 386 base::ListValue expected_args; 387 expected_args.Append(param.DeepCopy()); 388 web_view.Verify("frame_id2", &expected_args, result.get()); 389 } 390 391 TEST(CommandsTest, FailedFindElements) { 392 Session session("id"); 393 FindElementWebView web_view(false, kElementNotExistsQueryOnce); 394 base::DictionaryValue params; 395 params.SetString("using", "id"); 396 params.SetString("value", "a"); 397 scoped_ptr<base::Value> result; 398 ASSERT_EQ( 399 kOk, 400 ExecuteFindElements(1, &session, &web_view, params, &result).code()); 401 base::ListValue* list; 402 ASSERT_TRUE(result->GetAsList(&list)); 403 ASSERT_EQ(0U, list->GetSize()); 404 } 405 406 TEST(CommandsTest, SuccessfulFindChildElement) { 407 FindElementWebView web_view(true, kElementExistsQueryTwice); 408 Session session("id"); 409 session.implicit_wait = base::TimeDelta::FromSeconds(1); 410 session.SwitchToSubFrame("frame_id3", std::string()); 411 base::DictionaryValue params; 412 params.SetString("using", "tag name"); 413 params.SetString("value", "div"); 414 std::string element_id = "1"; 415 scoped_ptr<base::Value> result; 416 ASSERT_EQ( 417 kOk, 418 ExecuteFindChildElement( 419 1, &session, &web_view, element_id, params, &result).code()); 420 base::DictionaryValue locator_param; 421 locator_param.SetString("tag name", "div"); 422 base::DictionaryValue root_element_param; 423 root_element_param.SetString("ELEMENT", element_id); 424 base::ListValue expected_args; 425 expected_args.Append(locator_param.DeepCopy()); 426 expected_args.Append(root_element_param.DeepCopy()); 427 web_view.Verify("frame_id3", &expected_args, result.get()); 428 } 429 430 TEST(CommandsTest, FailedFindChildElement) { 431 Session session("id"); 432 FindElementWebView web_view(true, kElementNotExistsQueryOnce); 433 base::DictionaryValue params; 434 params.SetString("using", "id"); 435 params.SetString("value", "a"); 436 std::string element_id = "1"; 437 scoped_ptr<base::Value> result; 438 ASSERT_EQ( 439 kNoSuchElement, 440 ExecuteFindChildElement( 441 1, &session, &web_view, element_id, params, &result).code()); 442 } 443 444 TEST(CommandsTest, SuccessfulFindChildElements) { 445 FindElementWebView web_view(false, kElementExistsQueryTwice); 446 Session session("id"); 447 session.implicit_wait = base::TimeDelta::FromSeconds(1); 448 session.SwitchToSubFrame("frame_id4", std::string()); 449 base::DictionaryValue params; 450 params.SetString("using", "class name"); 451 params.SetString("value", "c"); 452 std::string element_id = "1"; 453 scoped_ptr<base::Value> result; 454 ASSERT_EQ( 455 kOk, 456 ExecuteFindChildElements( 457 1, &session, &web_view, element_id, params, &result).code()); 458 base::DictionaryValue locator_param; 459 locator_param.SetString("class name", "c"); 460 base::DictionaryValue root_element_param; 461 root_element_param.SetString("ELEMENT", element_id); 462 base::ListValue expected_args; 463 expected_args.Append(locator_param.DeepCopy()); 464 expected_args.Append(root_element_param.DeepCopy()); 465 web_view.Verify("frame_id4", &expected_args, result.get()); 466 } 467 468 TEST(CommandsTest, FailedFindChildElements) { 469 Session session("id"); 470 FindElementWebView web_view(false, kElementNotExistsQueryOnce); 471 base::DictionaryValue params; 472 params.SetString("using", "id"); 473 params.SetString("value", "a"); 474 std::string element_id = "1"; 475 scoped_ptr<base::Value> result; 476 ASSERT_EQ( 477 kOk, 478 ExecuteFindChildElements( 479 1, &session, &web_view, element_id, params, &result).code()); 480 base::ListValue* list; 481 ASSERT_TRUE(result->GetAsList(&list)); 482 ASSERT_EQ(0U, list->GetSize()); 483 } 484 485 TEST(CommandsTest, TimeoutInFindElement) { 486 Session session("id"); 487 FindElementWebView web_view(true, kElementExistsTimeout); 488 session.implicit_wait = base::TimeDelta::FromMilliseconds(2); 489 base::DictionaryValue params; 490 params.SetString("using", "id"); 491 params.SetString("value", "a"); 492 params.SetString("id", "1"); 493 scoped_ptr<base::Value> result; 494 ASSERT_EQ(kNoSuchElement, 495 ExecuteFindElement(1, &session, &web_view, params, &result).code()); 496 } 497 498 namespace { 499 500 class ErrorCallFunctionWebView : public StubWebView { 501 public: 502 explicit ErrorCallFunctionWebView(StatusCode code) 503 : StubWebView("1"), code_(code) {} 504 virtual ~ErrorCallFunctionWebView() {} 505 506 // Overridden from WebView: 507 virtual Status CallFunction(const std::string& frame, 508 const std::string& function, 509 const base::ListValue& args, 510 scoped_ptr<base::Value>* result) OVERRIDE { 511 return Status(code_); 512 } 513 514 private: 515 StatusCode code_; 516 }; 517 518 } // namespace 519 520 TEST(CommandsTest, ErrorFindElement) { 521 Session session("id"); 522 ErrorCallFunctionWebView web_view(kUnknownError); 523 base::DictionaryValue params; 524 params.SetString("using", "id"); 525 params.SetString("value", "a"); 526 scoped_ptr<base::Value> value; 527 ASSERT_EQ(kUnknownError, 528 ExecuteFindElement(1, &session, &web_view, params, &value).code()); 529 ASSERT_EQ(kUnknownError, 530 ExecuteFindElements(1, &session, &web_view, params, &value).code()); 531 } 532 533 TEST(CommandsTest, ErrorFindChildElement) { 534 Session session("id"); 535 ErrorCallFunctionWebView web_view(kStaleElementReference); 536 base::DictionaryValue params; 537 params.SetString("using", "id"); 538 params.SetString("value", "a"); 539 std::string element_id = "1"; 540 scoped_ptr<base::Value> result; 541 ASSERT_EQ( 542 kStaleElementReference, 543 ExecuteFindChildElement( 544 1, &session, &web_view, element_id, params, &result).code()); 545 ASSERT_EQ( 546 kStaleElementReference, 547 ExecuteFindChildElements( 548 1, &session, &web_view, element_id, params, &result).code()); 549 } 550 551 namespace { 552 553 class MockCommandListener : public CommandListener { 554 public: 555 MockCommandListener() : called_(false) {} 556 virtual ~MockCommandListener() {} 557 558 virtual Status BeforeCommand(const std::string& command_name) OVERRIDE { 559 called_ = true; 560 EXPECT_STREQ("cmd", command_name.c_str()); 561 return Status(kOk); 562 } 563 564 void VerifyCalled() { 565 EXPECT_TRUE(called_); 566 } 567 568 void VerifyNotCalled() { 569 EXPECT_FALSE(called_); 570 } 571 572 private: 573 bool called_; 574 }; 575 576 Status ExecuteAddListenerToSessionCommand( 577 CommandListener* listener, 578 Session* session, 579 const base::DictionaryValue& params, 580 scoped_ptr<base::Value>* return_value) { 581 session->command_listeners.push_back(listener); 582 return Status(kOk); 583 } 584 585 Status ExecuteQuitSessionCommand( 586 Session* session, 587 const base::DictionaryValue& params, 588 scoped_ptr<base::Value>* return_value) { 589 session->quit = true; 590 return Status(kOk); 591 } 592 593 void OnSessionCommand( 594 base::RunLoop* run_loop, 595 const Status& status, 596 scoped_ptr<base::Value> value, 597 const std::string& session_id) { 598 ASSERT_EQ(kOk, status.code()); 599 run_loop->Quit(); 600 } 601 602 } // namespace 603 604 TEST(CommandsTest, SuccessNotifyingCommandListeners) { 605 SessionThreadMap map; 606 linked_ptr<base::Thread> thread(new base::Thread("1")); 607 ASSERT_TRUE(thread->Start()); 608 std::string id("id"); 609 thread->message_loop()->PostTask( 610 FROM_HERE, 611 base::Bind(&internal::CreateSessionOnSessionThreadForTesting, id)); 612 613 map[id] = thread; 614 615 base::DictionaryValue params; 616 scoped_ptr<MockCommandListener> listener(new MockCommandListener()); 617 CommandListenerProxy* proxy = new CommandListenerProxy(listener.get()); 618 // We add |proxy| to the session instead of adding |listener| directly so that 619 // after the session is destroyed by ExecuteQuitSessionCommand, we can still 620 // verify the listener was called. The session owns and will destroy |proxy|. 621 SessionCommand cmd = base::Bind(&ExecuteAddListenerToSessionCommand, proxy); 622 base::MessageLoop loop; 623 base::RunLoop run_loop_addlistener; 624 625 // |CommandListener|s are notified immediately before commands are run. 626 // Here, the command adds |listener| to the session, so |listener| 627 // should not be notified since it will not have been added yet. 628 ExecuteSessionCommand( 629 &map, 630 "cmd", 631 cmd, 632 false, 633 params, 634 id, 635 base::Bind(&OnSessionCommand, &run_loop_addlistener)); 636 run_loop_addlistener.Run(); 637 638 listener->VerifyNotCalled(); 639 640 base::RunLoop run_loop_testlistener; 641 cmd = base::Bind(&ExecuteQuitSessionCommand); 642 643 // |listener| was added to |session| by ExecuteAddListenerToSessionCommand 644 // and should be notified before the next command, ExecuteQuitSessionCommand. 645 ExecuteSessionCommand( 646 &map, 647 "cmd", 648 cmd, 649 false, 650 params, 651 id, 652 base::Bind(&OnSessionCommand, &run_loop_testlistener)); 653 run_loop_testlistener.Run(); 654 655 listener->VerifyCalled(); 656 } 657 658 namespace { 659 660 class FailingCommandListener : public CommandListener { 661 public: 662 FailingCommandListener() {} 663 virtual ~FailingCommandListener() {} 664 665 virtual Status BeforeCommand(const std::string& command_name) OVERRIDE { 666 return Status(kUnknownError); 667 } 668 }; 669 670 void AddListenerToSessionIfSessionExists(CommandListener* listener) { 671 Session* session = GetThreadLocalSession(); 672 if (session) { 673 session->command_listeners.push_back(listener); 674 } 675 } 676 677 void OnFailBecauseErrorNotifyingListeners( 678 base::RunLoop* run_loop, 679 const Status& status, 680 scoped_ptr<base::Value> value, 681 const std::string& session_id) { 682 EXPECT_EQ(kUnknownError, status.code()); 683 EXPECT_FALSE(value.get()); 684 run_loop->Quit(); 685 } 686 687 void VerifySessionWasDeleted() { 688 ASSERT_FALSE(GetThreadLocalSession()); 689 } 690 691 } // namespace 692 693 TEST(CommandsTest, ErrorNotifyingCommandListeners) { 694 SessionThreadMap map; 695 linked_ptr<base::Thread> thread(new base::Thread("1")); 696 ASSERT_TRUE(thread->Start()); 697 std::string id("id"); 698 thread->message_loop()->PostTask( 699 FROM_HERE, 700 base::Bind(&internal::CreateSessionOnSessionThreadForTesting, id)); 701 map[id] = thread; 702 703 // In SuccessNotifyingCommandListenersBeforeCommand, we verified BeforeCommand 704 // was called before (as opposed to after) command execution. We don't need to 705 // verify this again, so we can just add |listener| with PostTask. 706 CommandListener* listener = new FailingCommandListener(); 707 thread->message_loop()->PostTask( 708 FROM_HERE, 709 base::Bind(&AddListenerToSessionIfSessionExists, listener)); 710 711 base::DictionaryValue params; 712 // The command should never be executed if BeforeCommand fails for a listener. 713 SessionCommand cmd = base::Bind(&ShouldNotBeCalled); 714 base::MessageLoop loop; 715 base::RunLoop run_loop; 716 717 ExecuteSessionCommand( 718 &map, 719 "cmd", 720 cmd, 721 false, 722 params, 723 id, 724 base::Bind(&OnFailBecauseErrorNotifyingListeners, &run_loop)); 725 run_loop.Run(); 726 727 thread->message_loop()->PostTask( 728 FROM_HERE, 729 base::Bind(&VerifySessionWasDeleted)); 730 } 731