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(¤t_time_, &tv, ¤t_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