Home | History | Annotate | Download | only in shill
      1 //
      2 // Copyright (C) 2012 The Android Open Source Project
      3 //
      4 // Licensed under the Apache License, Version 2.0 (the "License");
      5 // you may not use this file except in compliance with the License.
      6 // You may obtain a copy of the License at
      7 //
      8 //      http://www.apache.org/licenses/LICENSE-2.0
      9 //
     10 // Unless required by applicable law or agreed to in writing, software
     11 // distributed under the License is distributed on an "AS IS" BASIS,
     12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 // See the License for the specific language governing permissions and
     14 // limitations under the License.
     15 //
     16 
     17 #include "shill/connectivity_trial.h"
     18 
     19 #include <memory>
     20 #include <string>
     21 
     22 #include <base/bind.h>
     23 #include <gmock/gmock.h>
     24 #include <gtest/gtest.h>
     25 
     26 #include "shill/mock_connection.h"
     27 #include "shill/mock_control.h"
     28 #include "shill/mock_device_info.h"
     29 #include "shill/mock_event_dispatcher.h"
     30 #include "shill/mock_http_request.h"
     31 #include "shill/net/mock_time.h"
     32 
     33 using base::Bind;
     34 using base::Callback;
     35 using base::Unretained;
     36 using std::string;
     37 using std::unique_ptr;
     38 using std::vector;
     39 using testing::_;
     40 using testing::AtLeast;
     41 using testing::DoAll;
     42 using testing::InSequence;
     43 using testing::Mock;
     44 using testing::NiceMock;
     45 using testing::Return;
     46 using testing::ReturnRef;
     47 using testing::SetArgumentPointee;
     48 using testing::StrictMock;
     49 using testing::Test;
     50 
     51 namespace shill {
     52 
     53 namespace {
     54 const char kBadURL[] = "badurl";
     55 const char kInterfaceName[] = "int0";
     56 const char kURL[] = "http://www.chromium.org";
     57 const char kDNSServer0[] = "8.8.8.8";
     58 const char kDNSServer1[] = "8.8.4.4";
     59 const char* kDNSServers[] = { kDNSServer0, kDNSServer1 };
     60 }  // namespace
     61 
     62 MATCHER_P(IsResult, result, "") {
     63   return (result.phase == arg.phase &&
     64           result.status == arg.status);
     65 }
     66 
     67 class ConnectivityTrialTest : public Test {
     68  public:
     69   ConnectivityTrialTest()
     70       : device_info_(
     71             new NiceMock<MockDeviceInfo>(&control_, nullptr, nullptr, nullptr)),
     72         connection_(new StrictMock<MockConnection>(device_info_.get())),
     73         connectivity_trial_(new ConnectivityTrial(
     74             connection_.get(), &dispatcher_, kTrialTimeout,
     75             callback_target_.result_callback())),
     76         interface_name_(kInterfaceName),
     77         dns_servers_(kDNSServers, kDNSServers + 2),
     78         http_request_(nullptr) {
     79     current_time_.tv_sec = current_time_.tv_usec = 0;
     80   }
     81 
     82   virtual void SetUp() {
     83     EXPECT_CALL(*connection_.get(), IsIPv6())
     84                 .WillRepeatedly(Return(false));
     85     EXPECT_CALL(*connection_.get(), interface_name())
     86         .WillRepeatedly(ReturnRef(interface_name_));
     87     EXPECT_CALL(time_, GetTimeMonotonic(_))
     88         .WillRepeatedly(Invoke(this, &ConnectivityTrialTest::GetTimeMonotonic));
     89     EXPECT_CALL(*connection_.get(), dns_servers())
     90         .WillRepeatedly(ReturnRef(dns_servers_));
     91     EXPECT_FALSE(connectivity_trial_->request_.get());
     92   }
     93 
     94   virtual void TearDown() {
     95     Mock::VerifyAndClearExpectations(&http_request_);
     96     if (connectivity_trial_->request_.get()) {
     97       EXPECT_CALL(*http_request(), Stop());
     98 
     99       // Delete the ConnectivityTrial while expectations still exist.
    100       connectivity_trial_.reset();
    101     }
    102   }
    103 
    104  protected:
    105   static const int kNumAttempts;
    106   static const int kTrialTimeout;
    107 
    108   class CallbackTarget {
    109    public:
    110     CallbackTarget()
    111         : result_callback_(Bind(&CallbackTarget::ResultCallback,
    112                                 Unretained(this))) {
    113     }
    114 
    115     MOCK_METHOD1(ResultCallback, void(ConnectivityTrial::Result result));
    116     Callback<void(ConnectivityTrial::Result)>& result_callback() {
    117       return result_callback_;
    118     }
    119 
    120    private:
    121     Callback<void(ConnectivityTrial::Result)> result_callback_;
    122   };
    123 
    124   void AssignHTTPRequest() {
    125     http_request_ = new StrictMock<MockHTTPRequest>(connection_);
    126     connectivity_trial_->request_.reset(http_request_);  // Passes ownership.
    127   }
    128 
    129   bool StartTrialWithDelay(const string& url_string, int delay) {
    130     bool ret = connectivity_trial_->Start(url_string, delay);
    131     if (ret) {
    132       AssignHTTPRequest();
    133     }
    134     return ret;
    135   }
    136 
    137   bool StartTrial(const string& url_string) {
    138     return StartTrialWithDelay(url_string, 0);
    139   }
    140 
    141   void StartTrialTask() {
    142     AssignHTTPRequest();
    143     EXPECT_CALL(*http_request(), Start(_, _, _))
    144         .WillOnce(Return(HTTPRequest::kResultInProgress));
    145     EXPECT_CALL(dispatcher(), PostDelayedTask(_, kTrialTimeout * 1000));
    146     connectivity_trial()->StartTrialTask();
    147   }
    148 
    149   void ExpectTrialReturn(const ConnectivityTrial::Result& result) {
    150     EXPECT_CALL(callback_target(), ResultCallback(IsResult(result)));
    151 
    152     // Expect the PortalDetector to stop the current request.
    153     EXPECT_CALL(*http_request(), Stop());
    154   }
    155 
    156   void TimeoutTrial() {
    157     connectivity_trial_->TimeoutTrialTask();
    158   }
    159 
    160   MockHTTPRequest* http_request() { return http_request_; }
    161   ConnectivityTrial* connectivity_trial() { return connectivity_trial_.get(); }
    162   MockEventDispatcher& dispatcher() { return dispatcher_; }
    163   CallbackTarget& callback_target() { return callback_target_; }
    164   ByteString& response_data() { return response_data_; }
    165 
    166   void ExpectReset() {
    167     EXPECT_TRUE(callback_target_.result_callback().
    168                 Equals(connectivity_trial_->trial_callback_));
    169     EXPECT_FALSE(connectivity_trial_->request_.get());
    170   }
    171 
    172   void ExpectTrialRetry(const ConnectivityTrial::Result& result, int delay) {
    173     EXPECT_CALL(callback_target(), ResultCallback(IsResult(result)));
    174 
    175     // Expect the ConnectivityTrial to stop the current request.
    176     EXPECT_CALL(*http_request(), Stop());
    177 
    178     // Expect the ConnectivityTrial to schedule the next attempt.
    179     EXPECT_CALL(dispatcher(), PostDelayedTask(_, delay));
    180   }
    181 
    182   void AdvanceTime(int milliseconds) {
    183     struct timeval tv = { milliseconds / 1000, (milliseconds % 1000) * 1000 };
    184     timeradd(&current_time_, &tv, &current_time_);
    185   }
    186 
    187   void StartTrial() {
    188     EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    189     EXPECT_TRUE(StartTrial(kURL));
    190 
    191     // Expect that the request will be started -- return failure.
    192     EXPECT_CALL(*http_request(), Start(_, _, _))
    193         .WillOnce(Return(HTTPRequest::kResultInProgress));
    194     EXPECT_CALL(dispatcher(), PostDelayedTask(
    195         _, kTrialTimeout * 1000));
    196 
    197     connectivity_trial()->StartTrialTask();
    198   }
    199 
    200   void AppendReadData(const string& read_data) {
    201     response_data_.Append(ByteString(read_data, false));
    202     connectivity_trial_->RequestReadCallback(response_data_);
    203   }
    204 
    205  private:
    206   int GetTimeMonotonic(struct timeval* tv) {
    207     *tv = current_time_;
    208     return 0;
    209   }
    210 
    211   StrictMock<MockEventDispatcher> dispatcher_;
    212   MockControl control_;
    213   unique_ptr<MockDeviceInfo> device_info_;
    214   scoped_refptr<MockConnection> connection_;
    215   CallbackTarget callback_target_;
    216   unique_ptr<ConnectivityTrial> connectivity_trial_;
    217   StrictMock<MockTime> time_;
    218   struct timeval current_time_;
    219   const string interface_name_;
    220   vector<string> dns_servers_;
    221   ByteString response_data_;
    222   MockHTTPRequest* http_request_;
    223 };
    224 
    225 // static
    226 const int ConnectivityTrialTest::kNumAttempts = 0;
    227 const int ConnectivityTrialTest::kTrialTimeout = 4;
    228 
    229 TEST_F(ConnectivityTrialTest, Constructor) {
    230   ExpectReset();
    231 }
    232 
    233 TEST_F(ConnectivityTrialTest, InvalidURL) {
    234   EXPECT_FALSE(connectivity_trial()->IsActive());
    235   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(0);
    236   EXPECT_FALSE(StartTrial(kBadURL));
    237   ExpectReset();
    238 
    239   EXPECT_FALSE(connectivity_trial()->Retry(0));
    240   EXPECT_FALSE(connectivity_trial()->IsActive());
    241 }
    242 
    243 TEST_F(ConnectivityTrialTest, IsActive) {
    244   // Before the trial is started, should not be active.
    245   EXPECT_FALSE(connectivity_trial()->IsActive());
    246 
    247   // Once the trial is started, IsActive should return true.
    248   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    249   EXPECT_TRUE(StartTrial(kURL));
    250   StartTrialTask();
    251   EXPECT_TRUE(connectivity_trial()->IsActive());
    252 
    253   // Finish the trial, IsActive should return false.
    254   EXPECT_CALL(*http_request(), Stop());
    255   connectivity_trial()->CompleteTrial(
    256       ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent,
    257                                 ConnectivityTrial::kStatusFailure));
    258   EXPECT_FALSE(connectivity_trial()->IsActive());
    259 }
    260 
    261 TEST_F(ConnectivityTrialTest, StartAttemptFailed) {
    262   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    263   EXPECT_TRUE(StartTrial(kURL));
    264 
    265   // Expect that the request will be started -- return failure.
    266   EXPECT_CALL(*http_request(), Start(_, _, _))
    267       .WillOnce(Return(HTTPRequest::kResultConnectionFailure));
    268   // Expect a failure to be relayed to the caller.
    269   EXPECT_CALL(callback_target(),
    270               ResultCallback(IsResult(
    271                   ConnectivityTrial::Result(
    272                       ConnectivityTrial::kPhaseConnection,
    273                       ConnectivityTrial::kStatusFailure))))
    274       .Times(1);
    275 
    276   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(0);
    277   EXPECT_CALL(*http_request(), Stop());
    278 
    279   connectivity_trial()->StartTrialTask();
    280 }
    281 
    282 TEST_F(ConnectivityTrialTest, StartRepeated) {
    283   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(1);
    284   EXPECT_TRUE(StartTrial(kURL));
    285 
    286   // A second call should cancel the existing trial and set up the new one.
    287   EXPECT_CALL(*http_request(), Stop());
    288   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 10)).Times(1);
    289   EXPECT_TRUE(StartTrialWithDelay(kURL, 10));
    290 }
    291 
    292 TEST_F(ConnectivityTrialTest, StartTrialAfterDelay) {
    293   const int kDelaySeconds = 123;
    294   // The trial should be delayed by kDelaySeconds.
    295   EXPECT_CALL(dispatcher(), PostDelayedTask(_, kDelaySeconds));
    296   EXPECT_TRUE(StartTrialWithDelay(kURL, kDelaySeconds));
    297 }
    298 
    299 TEST_F(ConnectivityTrialTest, TrialRetry) {
    300   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    301   EXPECT_TRUE(StartTrial(kURL));
    302 
    303   // Expect that the request will be started -- return failure.
    304   EXPECT_CALL(*http_request(), Start(_, _, _))
    305       .WillOnce(Return(HTTPRequest::kResultConnectionFailure));
    306   EXPECT_CALL(*http_request(), Stop());
    307   connectivity_trial()->StartTrialTask();
    308 
    309   const int kRetryDelay = 7;
    310   EXPECT_CALL(*http_request(), Stop());
    311   EXPECT_CALL(dispatcher(), PostDelayedTask(_, kRetryDelay)).Times(1);
    312   EXPECT_TRUE(connectivity_trial()->Retry(kRetryDelay));
    313 }
    314 
    315 TEST_F(ConnectivityTrialTest, TrialRetryFail) {
    316   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    317   EXPECT_TRUE(StartTrial(kURL));
    318 
    319   EXPECT_CALL(*http_request(), Stop());
    320   connectivity_trial()->Stop();
    321 
    322   EXPECT_FALSE(connectivity_trial()->Retry(0));
    323 }
    324 
    325 // Exactly like AttemptCount, except that the termination conditions are
    326 // different because we're triggering a different sort of error.
    327 TEST_F(ConnectivityTrialTest, ReadBadHeadersRetry) {
    328   int num_failures = 3;
    329   int sec_between_attempts = 3;
    330 
    331   // Expect ConnectivityTrial to immediately post a task for the each attempt.
    332   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    333   EXPECT_TRUE(StartTrial(kURL));
    334 
    335   // Expect that the request will be started and return the in progress status.
    336   EXPECT_CALL(*http_request(), Start(_, _, _))
    337       .Times(num_failures).WillRepeatedly(
    338           Return(HTTPRequest::kResultInProgress));
    339 
    340   // Each HTTP request that gets started will have a request timeout.
    341   EXPECT_CALL(dispatcher(), PostDelayedTask(_, kTrialTimeout * 1000))
    342       .Times(num_failures);
    343 
    344   // Expect failures for all attempts but the last.
    345   EXPECT_CALL(callback_target(),
    346               ResultCallback(IsResult(
    347                   ConnectivityTrial::Result(
    348                       ConnectivityTrial::kPhaseContent,
    349                       ConnectivityTrial::kStatusFailure))))
    350       .Times(num_failures);
    351 
    352   // Expect the ConnectivityTrial to stop the current request each time, plus
    353   // an extra time in ConnectivityTrial::Stop().
    354   ByteString response_data("X", 1);
    355 
    356   for (int i = 0; i < num_failures; ++i) {
    357     connectivity_trial()->StartTrialTask();
    358     AdvanceTime(sec_between_attempts * 1000);
    359     EXPECT_CALL(*http_request(), Stop()).Times(2);
    360     EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(1);
    361     connectivity_trial()->RequestReadCallback(response_data);
    362     EXPECT_TRUE(connectivity_trial()->Retry(0));
    363   }
    364 }
    365 
    366 
    367 TEST_F(ConnectivityTrialTest, ReadBadHeader) {
    368   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    369   EXPECT_TRUE(StartTrial(kURL));
    370 
    371   StartTrialTask();
    372 
    373   ExpectTrialReturn(ConnectivityTrial::Result(
    374       ConnectivityTrial::kPhaseContent,
    375       ConnectivityTrial::kStatusFailure));
    376   AppendReadData("X");
    377 }
    378 
    379 TEST_F(ConnectivityTrialTest, RequestTimeout) {
    380   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    381   EXPECT_TRUE(StartTrial(kURL));
    382 
    383   StartTrialTask();
    384 
    385   ExpectTrialReturn(ConnectivityTrial::Result(
    386       ConnectivityTrial::kPhaseUnknown,
    387       ConnectivityTrial::kStatusTimeout));
    388 
    389   EXPECT_CALL(*http_request(), response_data())
    390       .WillOnce(ReturnRef(response_data()));
    391 
    392   TimeoutTrial();
    393 }
    394 
    395 TEST_F(ConnectivityTrialTest, ReadPartialHeaderTimeout) {
    396   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    397   EXPECT_TRUE(StartTrial(kURL));
    398 
    399   StartTrialTask();
    400 
    401 
    402   const string response_expected(ConnectivityTrial::kResponseExpected);
    403   const size_t partial_size = response_expected.length() / 2;
    404   AppendReadData(response_expected.substr(0, partial_size));
    405 
    406   ExpectTrialReturn(ConnectivityTrial::Result(
    407       ConnectivityTrial::kPhaseContent,
    408       ConnectivityTrial::kStatusTimeout));
    409 
    410   EXPECT_CALL(*http_request(), response_data())
    411       .WillOnce(ReturnRef(response_data()));
    412 
    413   TimeoutTrial();
    414 }
    415 
    416 TEST_F(ConnectivityTrialTest, ReadCompleteHeader) {
    417   const string response_expected(ConnectivityTrial::kResponseExpected);
    418   const size_t partial_size = response_expected.length() / 2;
    419 
    420   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    421   EXPECT_TRUE(StartTrial(kURL));
    422 
    423   StartTrialTask();
    424 
    425   AppendReadData(response_expected.substr(0, partial_size));
    426 
    427   ExpectTrialReturn(ConnectivityTrial::Result(
    428       ConnectivityTrial::kPhaseContent,
    429       ConnectivityTrial::kStatusSuccess));
    430 
    431   AppendReadData(response_expected.substr(partial_size));
    432 }
    433 
    434 TEST_F(ConnectivityTrialTest, ReadMatchingHeader) {
    435   const string kResponse("HTTP/9.8 204");
    436 
    437   EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
    438   EXPECT_TRUE(StartTrial(kURL));
    439 
    440   StartTrialTask();
    441 
    442   ExpectTrialReturn(ConnectivityTrial::Result(
    443       ConnectivityTrial::kPhaseContent,
    444       ConnectivityTrial::kStatusSuccess));
    445 
    446   AppendReadData(kResponse);
    447 }
    448 
    449 struct ResultMapping {
    450   ResultMapping() : http_result(HTTPRequest::kResultUnknown), trial_result() {}
    451   ResultMapping(HTTPRequest::Result in_http_result,
    452                 const ConnectivityTrial::Result& in_trial_result)
    453       : http_result(in_http_result),
    454         trial_result(in_trial_result) {}
    455   HTTPRequest::Result http_result;
    456   ConnectivityTrial::Result trial_result;
    457 };
    458 
    459 class ConnectivityTrialResultMappingTest
    460     : public testing::TestWithParam<ResultMapping> {};
    461 
    462 TEST_P(ConnectivityTrialResultMappingTest, MapResult) {
    463   ConnectivityTrial::Result trial_result =
    464       ConnectivityTrial::GetPortalResultForRequestResult(
    465           GetParam().http_result);
    466   EXPECT_EQ(trial_result.phase, GetParam().trial_result.phase);
    467   EXPECT_EQ(trial_result.status, GetParam().trial_result.status);
    468 }
    469 
    470 INSTANTIATE_TEST_CASE_P(
    471     TrialResultMappingTest,
    472     ConnectivityTrialResultMappingTest,
    473     ::testing::Values(
    474         ResultMapping(
    475             HTTPRequest::kResultUnknown,
    476             ConnectivityTrial::Result(ConnectivityTrial::kPhaseUnknown,
    477                                       ConnectivityTrial::kStatusFailure)),
    478         ResultMapping(
    479             HTTPRequest::kResultInProgress,
    480             ConnectivityTrial::Result(ConnectivityTrial::kPhaseUnknown,
    481                                       ConnectivityTrial::kStatusFailure)),
    482         ResultMapping(
    483             HTTPRequest::kResultDNSFailure,
    484             ConnectivityTrial::Result(ConnectivityTrial::kPhaseDNS,
    485                                       ConnectivityTrial::kStatusFailure)),
    486         ResultMapping(
    487             HTTPRequest::kResultDNSTimeout,
    488             ConnectivityTrial::Result(ConnectivityTrial::kPhaseDNS,
    489                                       ConnectivityTrial::kStatusTimeout)),
    490         ResultMapping(
    491             HTTPRequest::kResultConnectionFailure,
    492             ConnectivityTrial::Result(ConnectivityTrial::kPhaseConnection,
    493                                       ConnectivityTrial::kStatusFailure)),
    494         ResultMapping(
    495             HTTPRequest::kResultConnectionTimeout,
    496             ConnectivityTrial::Result(ConnectivityTrial::kPhaseConnection,
    497                                       ConnectivityTrial::kStatusTimeout)),
    498         ResultMapping(
    499             HTTPRequest::kResultRequestFailure,
    500             ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
    501                                       ConnectivityTrial::kStatusFailure)),
    502         ResultMapping(
    503             HTTPRequest::kResultRequestTimeout,
    504             ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
    505                                       ConnectivityTrial::kStatusTimeout)),
    506         ResultMapping(
    507             HTTPRequest::kResultResponseFailure,
    508             ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
    509                                       ConnectivityTrial::kStatusFailure)),
    510         ResultMapping(
    511             HTTPRequest::kResultResponseTimeout,
    512             ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
    513                                       ConnectivityTrial::kStatusTimeout)),
    514         ResultMapping(
    515             HTTPRequest::kResultSuccess,
    516             ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent,
    517                                       ConnectivityTrial::kStatusFailure))));
    518 
    519 }  // namespace shill
    520