519 lines
17 KiB
C++
519 lines
17 KiB
C++
//
|
|
// 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/connectivity_trial.h"
|
|
|
|
#include <memory>
|
|
#include <string>
|
|
|
|
#include <base/bind.h>
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "shill/mock_connection.h"
|
|
#include "shill/mock_control.h"
|
|
#include "shill/mock_device_info.h"
|
|
#include "shill/mock_event_dispatcher.h"
|
|
#include "shill/mock_http_request.h"
|
|
#include "shill/net/mock_time.h"
|
|
|
|
using base::Bind;
|
|
using base::Callback;
|
|
using base::Unretained;
|
|
using std::string;
|
|
using std::unique_ptr;
|
|
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.phase == arg.phase &&
|
|
result.status == arg.status);
|
|
}
|
|
|
|
class ConnectivityTrialTest : public Test {
|
|
public:
|
|
ConnectivityTrialTest()
|
|
: device_info_(
|
|
new NiceMock<MockDeviceInfo>(&control_, nullptr, nullptr, nullptr)),
|
|
connection_(new StrictMock<MockConnection>(device_info_.get())),
|
|
connectivity_trial_(new ConnectivityTrial(
|
|
connection_.get(), &dispatcher_, kTrialTimeout,
|
|
callback_target_.result_callback())),
|
|
interface_name_(kInterfaceName),
|
|
dns_servers_(kDNSServers, kDNSServers + 2),
|
|
http_request_(nullptr) {
|
|
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_));
|
|
EXPECT_CALL(time_, GetTimeMonotonic(_))
|
|
.WillRepeatedly(Invoke(this, &ConnectivityTrialTest::GetTimeMonotonic));
|
|
EXPECT_CALL(*connection_.get(), dns_servers())
|
|
.WillRepeatedly(ReturnRef(dns_servers_));
|
|
EXPECT_FALSE(connectivity_trial_->request_.get());
|
|
}
|
|
|
|
virtual void TearDown() {
|
|
Mock::VerifyAndClearExpectations(&http_request_);
|
|
if (connectivity_trial_->request_.get()) {
|
|
EXPECT_CALL(*http_request(), Stop());
|
|
|
|
// Delete the ConnectivityTrial while expectations still exist.
|
|
connectivity_trial_.reset();
|
|
}
|
|
}
|
|
|
|
protected:
|
|
static const int kNumAttempts;
|
|
static const int kTrialTimeout;
|
|
|
|
class CallbackTarget {
|
|
public:
|
|
CallbackTarget()
|
|
: result_callback_(Bind(&CallbackTarget::ResultCallback,
|
|
Unretained(this))) {
|
|
}
|
|
|
|
MOCK_METHOD1(ResultCallback, void(ConnectivityTrial::Result result));
|
|
Callback<void(ConnectivityTrial::Result)>& result_callback() {
|
|
return result_callback_;
|
|
}
|
|
|
|
private:
|
|
Callback<void(ConnectivityTrial::Result)> result_callback_;
|
|
};
|
|
|
|
void AssignHTTPRequest() {
|
|
http_request_ = new StrictMock<MockHTTPRequest>(connection_);
|
|
connectivity_trial_->request_.reset(http_request_); // Passes ownership.
|
|
}
|
|
|
|
bool StartTrialWithDelay(const string& url_string, int delay) {
|
|
bool ret = connectivity_trial_->Start(url_string, delay);
|
|
if (ret) {
|
|
AssignHTTPRequest();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool StartTrial(const string& url_string) {
|
|
return StartTrialWithDelay(url_string, 0);
|
|
}
|
|
|
|
void StartTrialTask() {
|
|
AssignHTTPRequest();
|
|
EXPECT_CALL(*http_request(), Start(_, _, _))
|
|
.WillOnce(Return(HTTPRequest::kResultInProgress));
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, kTrialTimeout * 1000));
|
|
connectivity_trial()->StartTrialTask();
|
|
}
|
|
|
|
void ExpectTrialReturn(const ConnectivityTrial::Result& result) {
|
|
EXPECT_CALL(callback_target(), ResultCallback(IsResult(result)));
|
|
|
|
// Expect the PortalDetector to stop the current request.
|
|
EXPECT_CALL(*http_request(), Stop());
|
|
}
|
|
|
|
void TimeoutTrial() {
|
|
connectivity_trial_->TimeoutTrialTask();
|
|
}
|
|
|
|
MockHTTPRequest* http_request() { return http_request_; }
|
|
ConnectivityTrial* connectivity_trial() { return connectivity_trial_.get(); }
|
|
MockEventDispatcher& dispatcher() { return dispatcher_; }
|
|
CallbackTarget& callback_target() { return callback_target_; }
|
|
ByteString& response_data() { return response_data_; }
|
|
|
|
void ExpectReset() {
|
|
EXPECT_TRUE(callback_target_.result_callback().
|
|
Equals(connectivity_trial_->trial_callback_));
|
|
EXPECT_FALSE(connectivity_trial_->request_.get());
|
|
}
|
|
|
|
void ExpectTrialRetry(const ConnectivityTrial::Result& result, int delay) {
|
|
EXPECT_CALL(callback_target(), ResultCallback(IsResult(result)));
|
|
|
|
// Expect the ConnectivityTrial to stop the current request.
|
|
EXPECT_CALL(*http_request(), Stop());
|
|
|
|
// Expect the ConnectivityTrial to schedule the next attempt.
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, delay));
|
|
}
|
|
|
|
void AdvanceTime(int milliseconds) {
|
|
struct timeval tv = { milliseconds / 1000, (milliseconds % 1000) * 1000 };
|
|
timeradd(¤t_time_, &tv, ¤t_time_);
|
|
}
|
|
|
|
void StartTrial() {
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
// Expect that the request will be started -- return failure.
|
|
EXPECT_CALL(*http_request(), Start(_, _, _))
|
|
.WillOnce(Return(HTTPRequest::kResultInProgress));
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(
|
|
_, kTrialTimeout * 1000));
|
|
|
|
connectivity_trial()->StartTrialTask();
|
|
}
|
|
|
|
void AppendReadData(const string& read_data) {
|
|
response_data_.Append(ByteString(read_data, false));
|
|
connectivity_trial_->RequestReadCallback(response_data_);
|
|
}
|
|
|
|
private:
|
|
int GetTimeMonotonic(struct timeval* tv) {
|
|
*tv = current_time_;
|
|
return 0;
|
|
}
|
|
|
|
StrictMock<MockEventDispatcher> dispatcher_;
|
|
MockControl control_;
|
|
unique_ptr<MockDeviceInfo> device_info_;
|
|
scoped_refptr<MockConnection> connection_;
|
|
CallbackTarget callback_target_;
|
|
unique_ptr<ConnectivityTrial> connectivity_trial_;
|
|
StrictMock<MockTime> time_;
|
|
struct timeval current_time_;
|
|
const string interface_name_;
|
|
vector<string> dns_servers_;
|
|
ByteString response_data_;
|
|
MockHTTPRequest* http_request_;
|
|
};
|
|
|
|
// static
|
|
const int ConnectivityTrialTest::kNumAttempts = 0;
|
|
const int ConnectivityTrialTest::kTrialTimeout = 4;
|
|
|
|
TEST_F(ConnectivityTrialTest, Constructor) {
|
|
ExpectReset();
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, InvalidURL) {
|
|
EXPECT_FALSE(connectivity_trial()->IsActive());
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(0);
|
|
EXPECT_FALSE(StartTrial(kBadURL));
|
|
ExpectReset();
|
|
|
|
EXPECT_FALSE(connectivity_trial()->Retry(0));
|
|
EXPECT_FALSE(connectivity_trial()->IsActive());
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, IsActive) {
|
|
// Before the trial is started, should not be active.
|
|
EXPECT_FALSE(connectivity_trial()->IsActive());
|
|
|
|
// Once the trial is started, IsActive should return true.
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
StartTrialTask();
|
|
EXPECT_TRUE(connectivity_trial()->IsActive());
|
|
|
|
// Finish the trial, IsActive should return false.
|
|
EXPECT_CALL(*http_request(), Stop());
|
|
connectivity_trial()->CompleteTrial(
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent,
|
|
ConnectivityTrial::kStatusFailure));
|
|
EXPECT_FALSE(connectivity_trial()->IsActive());
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, StartAttemptFailed) {
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
// Expect that the request will be started -- return failure.
|
|
EXPECT_CALL(*http_request(), Start(_, _, _))
|
|
.WillOnce(Return(HTTPRequest::kResultConnectionFailure));
|
|
// Expect a failure to be relayed to the caller.
|
|
EXPECT_CALL(callback_target(),
|
|
ResultCallback(IsResult(
|
|
ConnectivityTrial::Result(
|
|
ConnectivityTrial::kPhaseConnection,
|
|
ConnectivityTrial::kStatusFailure))))
|
|
.Times(1);
|
|
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(0);
|
|
EXPECT_CALL(*http_request(), Stop());
|
|
|
|
connectivity_trial()->StartTrialTask();
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, StartRepeated) {
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(1);
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
// A second call should cancel the existing trial and set up the new one.
|
|
EXPECT_CALL(*http_request(), Stop());
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 10)).Times(1);
|
|
EXPECT_TRUE(StartTrialWithDelay(kURL, 10));
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, StartTrialAfterDelay) {
|
|
const int kDelaySeconds = 123;
|
|
// The trial should be delayed by kDelaySeconds.
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, kDelaySeconds));
|
|
EXPECT_TRUE(StartTrialWithDelay(kURL, kDelaySeconds));
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, TrialRetry) {
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
// Expect that the request will be started -- return failure.
|
|
EXPECT_CALL(*http_request(), Start(_, _, _))
|
|
.WillOnce(Return(HTTPRequest::kResultConnectionFailure));
|
|
EXPECT_CALL(*http_request(), Stop());
|
|
connectivity_trial()->StartTrialTask();
|
|
|
|
const int kRetryDelay = 7;
|
|
EXPECT_CALL(*http_request(), Stop());
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, kRetryDelay)).Times(1);
|
|
EXPECT_TRUE(connectivity_trial()->Retry(kRetryDelay));
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, TrialRetryFail) {
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
EXPECT_CALL(*http_request(), Stop());
|
|
connectivity_trial()->Stop();
|
|
|
|
EXPECT_FALSE(connectivity_trial()->Retry(0));
|
|
}
|
|
|
|
// Exactly like AttemptCount, except that the termination conditions are
|
|
// different because we're triggering a different sort of error.
|
|
TEST_F(ConnectivityTrialTest, ReadBadHeadersRetry) {
|
|
int num_failures = 3;
|
|
int sec_between_attempts = 3;
|
|
|
|
// Expect ConnectivityTrial to immediately post a task for the each attempt.
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
// Expect that the request will be started and return the in progress status.
|
|
EXPECT_CALL(*http_request(), Start(_, _, _))
|
|
.Times(num_failures).WillRepeatedly(
|
|
Return(HTTPRequest::kResultInProgress));
|
|
|
|
// Each HTTP request that gets started will have a request timeout.
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, kTrialTimeout * 1000))
|
|
.Times(num_failures);
|
|
|
|
// Expect failures for all attempts but the last.
|
|
EXPECT_CALL(callback_target(),
|
|
ResultCallback(IsResult(
|
|
ConnectivityTrial::Result(
|
|
ConnectivityTrial::kPhaseContent,
|
|
ConnectivityTrial::kStatusFailure))))
|
|
.Times(num_failures);
|
|
|
|
// Expect the ConnectivityTrial to stop the current request each time, plus
|
|
// an extra time in ConnectivityTrial::Stop().
|
|
ByteString response_data("X", 1);
|
|
|
|
for (int i = 0; i < num_failures; ++i) {
|
|
connectivity_trial()->StartTrialTask();
|
|
AdvanceTime(sec_between_attempts * 1000);
|
|
EXPECT_CALL(*http_request(), Stop()).Times(2);
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0)).Times(1);
|
|
connectivity_trial()->RequestReadCallback(response_data);
|
|
EXPECT_TRUE(connectivity_trial()->Retry(0));
|
|
}
|
|
}
|
|
|
|
|
|
TEST_F(ConnectivityTrialTest, ReadBadHeader) {
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
StartTrialTask();
|
|
|
|
ExpectTrialReturn(ConnectivityTrial::Result(
|
|
ConnectivityTrial::kPhaseContent,
|
|
ConnectivityTrial::kStatusFailure));
|
|
AppendReadData("X");
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, RequestTimeout) {
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
StartTrialTask();
|
|
|
|
ExpectTrialReturn(ConnectivityTrial::Result(
|
|
ConnectivityTrial::kPhaseUnknown,
|
|
ConnectivityTrial::kStatusTimeout));
|
|
|
|
EXPECT_CALL(*http_request(), response_data())
|
|
.WillOnce(ReturnRef(response_data()));
|
|
|
|
TimeoutTrial();
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, ReadPartialHeaderTimeout) {
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
StartTrialTask();
|
|
|
|
|
|
const string response_expected(ConnectivityTrial::kResponseExpected);
|
|
const size_t partial_size = response_expected.length() / 2;
|
|
AppendReadData(response_expected.substr(0, partial_size));
|
|
|
|
ExpectTrialReturn(ConnectivityTrial::Result(
|
|
ConnectivityTrial::kPhaseContent,
|
|
ConnectivityTrial::kStatusTimeout));
|
|
|
|
EXPECT_CALL(*http_request(), response_data())
|
|
.WillOnce(ReturnRef(response_data()));
|
|
|
|
TimeoutTrial();
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, ReadCompleteHeader) {
|
|
const string response_expected(ConnectivityTrial::kResponseExpected);
|
|
const size_t partial_size = response_expected.length() / 2;
|
|
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
StartTrialTask();
|
|
|
|
AppendReadData(response_expected.substr(0, partial_size));
|
|
|
|
ExpectTrialReturn(ConnectivityTrial::Result(
|
|
ConnectivityTrial::kPhaseContent,
|
|
ConnectivityTrial::kStatusSuccess));
|
|
|
|
AppendReadData(response_expected.substr(partial_size));
|
|
}
|
|
|
|
TEST_F(ConnectivityTrialTest, ReadMatchingHeader) {
|
|
const string kResponse("HTTP/9.8 204");
|
|
|
|
EXPECT_CALL(dispatcher(), PostDelayedTask(_, 0));
|
|
EXPECT_TRUE(StartTrial(kURL));
|
|
|
|
StartTrialTask();
|
|
|
|
ExpectTrialReturn(ConnectivityTrial::Result(
|
|
ConnectivityTrial::kPhaseContent,
|
|
ConnectivityTrial::kStatusSuccess));
|
|
|
|
AppendReadData(kResponse);
|
|
}
|
|
|
|
struct ResultMapping {
|
|
ResultMapping() : http_result(HTTPRequest::kResultUnknown), trial_result() {}
|
|
ResultMapping(HTTPRequest::Result in_http_result,
|
|
const ConnectivityTrial::Result& in_trial_result)
|
|
: http_result(in_http_result),
|
|
trial_result(in_trial_result) {}
|
|
HTTPRequest::Result http_result;
|
|
ConnectivityTrial::Result trial_result;
|
|
};
|
|
|
|
class ConnectivityTrialResultMappingTest
|
|
: public testing::TestWithParam<ResultMapping> {};
|
|
|
|
TEST_P(ConnectivityTrialResultMappingTest, MapResult) {
|
|
ConnectivityTrial::Result trial_result =
|
|
ConnectivityTrial::GetPortalResultForRequestResult(
|
|
GetParam().http_result);
|
|
EXPECT_EQ(trial_result.phase, GetParam().trial_result.phase);
|
|
EXPECT_EQ(trial_result.status, GetParam().trial_result.status);
|
|
}
|
|
|
|
INSTANTIATE_TEST_CASE_P(
|
|
TrialResultMappingTest,
|
|
ConnectivityTrialResultMappingTest,
|
|
::testing::Values(
|
|
ResultMapping(
|
|
HTTPRequest::kResultUnknown,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseUnknown,
|
|
ConnectivityTrial::kStatusFailure)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultInProgress,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseUnknown,
|
|
ConnectivityTrial::kStatusFailure)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultDNSFailure,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseDNS,
|
|
ConnectivityTrial::kStatusFailure)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultDNSTimeout,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseDNS,
|
|
ConnectivityTrial::kStatusTimeout)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultConnectionFailure,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseConnection,
|
|
ConnectivityTrial::kStatusFailure)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultConnectionTimeout,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseConnection,
|
|
ConnectivityTrial::kStatusTimeout)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultRequestFailure,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
|
|
ConnectivityTrial::kStatusFailure)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultRequestTimeout,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
|
|
ConnectivityTrial::kStatusTimeout)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultResponseFailure,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
|
|
ConnectivityTrial::kStatusFailure)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultResponseTimeout,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseHTTP,
|
|
ConnectivityTrial::kStatusTimeout)),
|
|
ResultMapping(
|
|
HTTPRequest::kResultSuccess,
|
|
ConnectivityTrial::Result(ConnectivityTrial::kPhaseContent,
|
|
ConnectivityTrial::kStatusFailure))));
|
|
|
|
} // namespace shill
|