// // Copyright (C) 2012 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "shill/portal_detector.h" #include #include #include #include #include #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(&control_, nullptr, nullptr, nullptr)), connection_(new StrictMock(device_info_.get())), portal_detector_( new PortalDetector(connection_.get(), &dispatcher_, callback_target_.result_callback())), connectivity_trial_(new StrictMock( 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& result_callback() { return result_callback_; } private: Callback 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 dispatcher_; MockControl control_; std::unique_ptr device_info_; scoped_refptr connection_; CallbackTarget callback_target_; std::unique_ptr portal_detector_; MockConnectivityTrial* connectivity_trial_; StrictMock time_; struct timeval current_time_; const string interface_name_; vector 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