| // Copyright (c) 2012 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/portal_detector.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include <base/bind.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "shill/connectivity_trial.h" |
| #include "shill/mock_connection.h" |
| #include "shill/mock_connectivity_trial.h" |
| #include "shill/mock_control.h" |
| #include "shill/mock_device_info.h" |
| #include "shill/mock_event_dispatcher.h" |
| #include "shill/net/mock_time.h" |
| |
| using base::Bind; |
| using base::Callback; |
| using base::Unretained; |
| using std::string; |
| using std::vector; |
| using testing::_; |
| using testing::AtLeast; |
| using testing::DoAll; |
| using testing::InSequence; |
| using testing::Mock; |
| using testing::NiceMock; |
| using testing::Return; |
| using testing::ReturnRef; |
| using testing::SetArgumentPointee; |
| using testing::StrictMock; |
| using testing::Test; |
| |
| namespace shill { |
| |
| namespace { |
| const char kBadURL[] = "badurl"; |
| const char kInterfaceName[] = "int0"; |
| const char kURL[] = "http://www.chromium.org"; |
| const char kDNSServer0[] = "8.8.8.8"; |
| const char kDNSServer1[] = "8.8.4.4"; |
| const char *kDNSServers[] = { kDNSServer0, kDNSServer1 }; |
| } // namespace |
| |
| MATCHER_P(IsResult, result, "") { |
| return (result.trial_result.phase == arg.trial_result.phase && |
| result.trial_result.status == arg.trial_result.status && |
| result.final == arg.final); |
| } |
| |
| |
| class PortalDetectorTest : public Test { |
| public: |
| PortalDetectorTest() |
| : device_info_( |
| new NiceMock<MockDeviceInfo>(&control_, nullptr, nullptr, nullptr)), |
| connection_(new StrictMock<MockConnection>(device_info_.get())), |
| portal_detector_( |
| new PortalDetector(connection_.get(), &dispatcher_, |
| callback_target_.result_callback())), |
| connectivity_trial_(new StrictMock<MockConnectivityTrial>( |
| connection_, PortalDetector::kRequestTimeoutSeconds)), |
| interface_name_(kInterfaceName), |
| dns_servers_(kDNSServers, kDNSServers + 2) { |
| current_time_.tv_sec = current_time_.tv_usec = 0; |
| } |
| |
| virtual void SetUp() { |
| EXPECT_CALL(*connection_.get(), IsIPv6()) |
| .WillRepeatedly(Return(false)); |
| EXPECT_CALL(*connection_.get(), interface_name()) |
| .WillRepeatedly(ReturnRef(interface_name_)); |
| portal_detector_->time_ = &time_; |
| EXPECT_CALL(time_, GetTimeMonotonic(_)) |
| .WillRepeatedly(Invoke(this, &PortalDetectorTest::GetTimeMonotonic)); |
| EXPECT_CALL(*connection_.get(), dns_servers()) |
| .WillRepeatedly(ReturnRef(dns_servers_)); |
| portal_detector_->connectivity_trial_ |
| .reset(connectivity_trial_); // Passes ownership |
| EXPECT_TRUE(portal_detector()->connectivity_trial_.get()); |
| } |
| |
| virtual void TearDown() { |
| if (portal_detector()->connectivity_trial_.get()) { |
| EXPECT_CALL(*connectivity_trial(), Stop()); |
| |
| // Delete the portal detector while expectations still exist. |
| portal_detector_.reset(); |
| } |
| } |
| |
| protected: |
| static const int kNumAttempts; |
| |
| class CallbackTarget { |
| public: |
| CallbackTarget() |
| : result_callback_(Bind(&CallbackTarget::ResultCallback, |
| Unretained(this))) { |
| } |
| |
| MOCK_METHOD1(ResultCallback, void(const PortalDetector::Result &result)); |
| Callback<void(const PortalDetector::Result &)> &result_callback() { |
| return result_callback_; |
| } |
| |
| private: |
| Callback<void(const PortalDetector::Result &)> result_callback_; |
| }; |
| |
| bool StartPortalRequest(const string &url_string) { |
| bool ret = portal_detector_->Start(url_string); |
| return ret; |
| } |
| |
| PortalDetector *portal_detector() { return portal_detector_.get(); } |
| MockConnectivityTrial *connectivity_trial() { return connectivity_trial_; } |
| MockEventDispatcher &dispatcher() { return dispatcher_; } |
| CallbackTarget &callback_target() { return callback_target_; } |
| |
| void ExpectReset() { |
| EXPECT_FALSE(portal_detector_->attempt_count_); |
| EXPECT_FALSE(portal_detector_->failures_in_content_phase_); |
| EXPECT_TRUE(callback_target_.result_callback(). |
| Equals(portal_detector_->portal_result_callback_)); |
| } |
| |
| void ExpectAttemptRetry(const PortalDetector::Result &result) { |
| EXPECT_CALL(callback_target(), |
| ResultCallback(IsResult(result))); |
| EXPECT_CALL(*connectivity_trial(), |
| Retry(PortalDetector::kMinTimeBetweenAttemptsSeconds * 1000)); |
| } |
| |
| void AdvanceTime(int milliseconds) { |
| struct timeval tv = { milliseconds / 1000, (milliseconds % 1000) * 1000 }; |
| timeradd(¤t_time_, &tv, ¤t_time_); |
| } |
| |
| void StartAttempt() { |
| EXPECT_CALL(*connectivity_trial(), Start(_, _)).WillOnce(Return(true)); |
| |
| EXPECT_TRUE(StartPortalRequest(kURL)); |
| } |
| |
| private: |
| int GetTimeMonotonic(struct timeval *tv) { |
| *tv = current_time_; |
| return 0; |
| } |
| |
| StrictMock<MockEventDispatcher> dispatcher_; |
| MockControl control_; |
| std::unique_ptr<MockDeviceInfo> device_info_; |
| scoped_refptr<MockConnection> connection_; |
| CallbackTarget callback_target_; |
| std::unique_ptr<PortalDetector> portal_detector_; |
| MockConnectivityTrial* connectivity_trial_; |
| StrictMock<MockTime> time_; |
| struct timeval current_time_; |
| const string interface_name_; |
| vector<string> dns_servers_; |
| }; |
| |
| // static |
| const int PortalDetectorTest::kNumAttempts = 0; |
| |
| TEST_F(PortalDetectorTest, Constructor) { |
| ExpectReset(); |
| } |
| |
| TEST_F(PortalDetectorTest, InvalidURL) { |
| EXPECT_CALL(*connectivity_trial(), Start(_, _)).WillOnce(Return(false)); |
| EXPECT_FALSE(portal_detector()->Start(kBadURL)); |
| ExpectReset(); |
| } |
| |
| TEST_F(PortalDetectorTest, StartAttemptFailed) { |
| EXPECT_CALL(*connectivity_trial(), Start(kURL, 0)).WillOnce(Return(true)); |
| EXPECT_TRUE(StartPortalRequest(kURL)); |
| |
| // Expect that the request will be started -- return failure. |
| ConnectivityTrial::Result errorResult = |
| ConnectivityTrial::GetPortalResultForRequestResult( |
| HTTPRequest::kResultConnectionFailure); |
| |
| // Expect a non-final failure to be relayed to the caller. |
| ExpectAttemptRetry( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseConnection, |
| ConnectivityTrial::kStatusFailure), |
| kNumAttempts, |
| false)); |
| |
| portal_detector()->CompleteAttempt(errorResult); |
| } |
| |
| TEST_F(PortalDetectorTest, IsInProgress) { |
| EXPECT_FALSE(portal_detector()->IsInProgress()); |
| // Starting the attempt immediately should result with IsInProgress returning |
| // true |
| EXPECT_CALL(*connectivity_trial(), Start(_, _)) |
| .Times(2).WillRepeatedly(Return(true)); |
| EXPECT_TRUE(StartPortalRequest(kURL)); |
| EXPECT_CALL(*connectivity_trial(), IsActive()).WillOnce(Return(true)); |
| EXPECT_TRUE(portal_detector()->IsInProgress()); |
| |
| // Starting the attempt with a delay should result with IsInProgress returning |
| // false |
| EXPECT_CALL(*connectivity_trial(), IsActive()).WillOnce(Return(false)); |
| portal_detector()->StartAfterDelay(kURL, 2); |
| EXPECT_FALSE(portal_detector()->IsInProgress()); |
| |
| // Advance time, IsInProgress should now be true |
| EXPECT_CALL(*connectivity_trial(), IsActive()).WillOnce(Return(true)); |
| AdvanceTime(2000); |
| EXPECT_TRUE(portal_detector()->IsInProgress()); |
| |
| // Times beyond the start time before the attempt finishes should also return |
| // true |
| EXPECT_CALL(*connectivity_trial(), IsActive()).WillOnce(Return(true)); |
| AdvanceTime(1000); |
| EXPECT_TRUE(portal_detector()->IsInProgress()); |
| } |
| |
| TEST_F(PortalDetectorTest, AdjustStartDelayImmediate) { |
| EXPECT_CALL(*connectivity_trial(), Start(kURL, 0)).WillOnce(Return(true)); |
| EXPECT_TRUE(StartPortalRequest(kURL)); |
| |
| // A second attempt should be delayed by kMinTimeBetweenAttemptsSeconds. |
| EXPECT_TRUE(portal_detector()->AdjustStartDelay(0) |
| == PortalDetector::kMinTimeBetweenAttemptsSeconds); |
| } |
| |
| TEST_F(PortalDetectorTest, AdjustStartDelayAfterDelay) { |
| const int kDelaySeconds = 123; |
| // The first attempt should be delayed by kDelaySeconds. |
| EXPECT_CALL(*connectivity_trial(), Start(kURL, kDelaySeconds * 1000)) |
| .WillOnce(Return(true)); |
| |
| portal_detector()->StartAfterDelay(kURL, kDelaySeconds); |
| |
| AdvanceTime(kDelaySeconds * 1000); |
| |
| // A second attempt should be delayed by kMinTimeBetweenAttemptsSeconds. |
| EXPECT_TRUE(portal_detector()->AdjustStartDelay(0) |
| == PortalDetector::kMinTimeBetweenAttemptsSeconds); |
| } |
| |
| TEST_F(PortalDetectorTest, AttemptCount) { |
| EXPECT_FALSE(portal_detector()->IsInProgress()); |
| // Expect the PortalDetector to immediately post a task for the each attempt. |
| EXPECT_CALL(*connectivity_trial(), Start(_, _)) |
| .Times(2).WillRepeatedly(Return(true)); |
| EXPECT_CALL(*connectivity_trial(), IsActive()).WillOnce(Return(false)); |
| portal_detector()->StartAfterDelay(kURL, 2); |
| |
| EXPECT_FALSE(portal_detector()->IsInProgress()); |
| |
| // Expect that the request will be started -- return failure. |
| EXPECT_CALL(*connectivity_trial(), Retry(0)) |
| .Times(PortalDetector::kMaxRequestAttempts - 1); |
| |
| { |
| InSequence s; |
| |
| // Expect non-final failures for all attempts but the last. |
| EXPECT_CALL(callback_target(), |
| ResultCallback(IsResult( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseDNS, |
| ConnectivityTrial::kStatusFailure), |
| kNumAttempts, |
| false)))) |
| .Times(PortalDetector::kMaxRequestAttempts - 1); |
| |
| // Expect a single final failure. |
| EXPECT_CALL(callback_target(), |
| ResultCallback(IsResult( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseDNS, |
| ConnectivityTrial::kStatusFailure), |
| kNumAttempts, |
| true)))) |
| .Times(1); |
| } |
| |
| // Expect the PortalDetector to stop the ConnectivityTrial after |
| // the final attempt. |
| EXPECT_CALL(*connectivity_trial(), Stop()).Times(1); |
| |
| EXPECT_CALL(*connectivity_trial(), IsActive()).WillOnce(Return(true)); |
| portal_detector()->Start(kURL); |
| for (int i = 0; i < PortalDetector::kMaxRequestAttempts; i++) { |
| EXPECT_TRUE(portal_detector()->IsInProgress()); |
| AdvanceTime(PortalDetector::kMinTimeBetweenAttemptsSeconds * 1000); |
| ConnectivityTrial::Result r = |
| ConnectivityTrial::GetPortalResultForRequestResult( |
| HTTPRequest::kResultDNSFailure); |
| portal_detector()->CompleteAttempt(r); |
| } |
| |
| EXPECT_FALSE(portal_detector()->IsInProgress()); |
| ExpectReset(); |
| } |
| |
| // Exactly like AttemptCount, except that the termination conditions are |
| // different because we're triggering a different sort of error. |
| TEST_F(PortalDetectorTest, ReadBadHeadersRetry) { |
| EXPECT_FALSE(portal_detector()->IsInProgress()); |
| // Expect the PortalDetector to immediately post a task for the each attempt. |
| EXPECT_CALL(*connectivity_trial(), Start(_, 0)) |
| .Times(2).WillRepeatedly(Return(true)); |
| |
| EXPECT_TRUE(StartPortalRequest(kURL)); |
| |
| // Expect that the request will be started -- return failure. |
| EXPECT_CALL(*connectivity_trial(), Retry(0)) |
| .Times(PortalDetector::kMaxFailuresInContentPhase - 1); |
| { |
| InSequence s; |
| |
| // Expect non-final failures for all attempts but the last. |
| EXPECT_CALL(callback_target(), |
| ResultCallback(IsResult( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusFailure), |
| kNumAttempts, |
| false)))) |
| .Times(PortalDetector::kMaxFailuresInContentPhase - 1); |
| |
| // Expect a single final failure. |
| EXPECT_CALL(callback_target(), |
| ResultCallback(IsResult( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusFailure), |
| kNumAttempts, |
| true)))) |
| .Times(1); |
| } |
| |
| // Expect the PortalDetector to stop the current request each time, plus |
| // an extra time in PortalDetector::Stop(). |
| EXPECT_CALL(*connectivity_trial(), Stop()).Times(1); |
| |
| EXPECT_CALL(*connectivity_trial(), IsActive()).WillOnce(Return(true)); |
| portal_detector()->Start(kURL); |
| for (int i = 0; i < PortalDetector::kMaxFailuresInContentPhase; i++) { |
| EXPECT_TRUE(portal_detector()->IsInProgress()); |
| AdvanceTime(PortalDetector::kMinTimeBetweenAttemptsSeconds * 1000); |
| ConnectivityTrial::Result r = |
| ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusFailure); |
| portal_detector()->CompleteAttempt(r); |
| } |
| |
| EXPECT_FALSE(portal_detector()->IsInProgress()); |
| } |
| |
| TEST_F(PortalDetectorTest, ReadBadHeader) { |
| StartAttempt(); |
| |
| ExpectAttemptRetry( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusFailure), |
| kNumAttempts, |
| false)); |
| |
| ConnectivityTrial::Result r = |
| ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusFailure); |
| portal_detector()->CompleteAttempt(r); |
| } |
| |
| TEST_F(PortalDetectorTest, RequestTimeout) { |
| StartAttempt(); |
| ExpectAttemptRetry( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseUnknown, |
| ConnectivityTrial::kStatusTimeout), |
| kNumAttempts, |
| false)); |
| |
| ConnectivityTrial::Result r = |
| ConnectivityTrial::Result(ConnectivityTrial::kPhaseUnknown, |
| ConnectivityTrial::kStatusTimeout); |
| portal_detector()->CompleteAttempt(r); |
| } |
| |
| TEST_F(PortalDetectorTest, ReadPartialHeaderTimeout) { |
| StartAttempt(); |
| |
| ExpectAttemptRetry( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusTimeout), |
| kNumAttempts, |
| false)); |
| |
| ConnectivityTrial::Result r = |
| ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusTimeout); |
| portal_detector()->CompleteAttempt(r); |
| } |
| |
| TEST_F(PortalDetectorTest, ReadCompleteHeader) { |
| StartAttempt(); |
| |
| EXPECT_CALL(callback_target(), |
| ResultCallback(IsResult( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusSuccess), |
| kNumAttempts, |
| true)))); |
| |
| EXPECT_CALL(*connectivity_trial(), Stop()).Times(1); |
| ConnectivityTrial::Result r = |
| ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusSuccess); |
| portal_detector()->CompleteAttempt(r); |
| } |
| |
| TEST_F(PortalDetectorTest, ReadMatchingHeader) { |
| StartAttempt(); |
| |
| EXPECT_CALL(callback_target(), |
| ResultCallback(IsResult( |
| PortalDetector::Result( |
| ConnectivityTrial::Result( |
| ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusSuccess), |
| kNumAttempts, |
| true)))); |
| EXPECT_CALL(*connectivity_trial(), Stop()).Times(1); |
| ConnectivityTrial::Result r = |
| ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent, |
| ConnectivityTrial::kStatusSuccess); |
| portal_detector()->CompleteAttempt(r); |
| } |
| |
| } // namespace shill |