Home | History | Annotate | Download | only in chromedriver
      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(&params));
    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, &params, &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