blob: 0cf992683fa4889d1703ea933d50cf3dee2d22c0 [file] [log] [blame]
// Copyright (c) 2013 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/connection_health_checker.h"
#include <arpa/inet.h>
#include <string>
#include <vector>
#include <base/bind.h>
#include <base/callback.h>
#include <base/cancelable_callback.h>
#include <base/memory/scoped_ptr.h>
#include <base/memory/scoped_vector.h>
#include <gtest/gtest.h>
#include "shill/mock_async_connection.h"
#include "shill/mock_connection.h"
#include "shill/mock_control.h"
#include "shill/mock_dns_client.h"
#include "shill/mock_dns_client_factory.h"
#include "shill/mock_device_info.h"
#include "shill/mock_event_dispatcher.h"
#include "shill/mock_ip_address_store.h"
#include "shill/mock_sockets.h"
#include "shill/mock_socket_info_reader.h"
using base::Bind;
using base::Callback;
using base::Closure;
using base::Unretained;
using std::string;
using std::vector;
using ::testing::AtLeast;
using ::testing::DoAll;
using ::testing::Gt;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SaveArg;
using ::testing::Sequence;
using ::testing::SetArgumentPointee;
using ::testing::StrictMock;
using ::testing::Test;
using ::testing::_;
namespace shill {
namespace {
const char kInterfaceName[] = "int0";
const char kIPAddress_0_0_0_0[] = "0.0.0.0";
const char kIPAddress_8_8_8_8[] = "8.8.8.8";
const char kProxyIPAddressRemote[] = "74.125.224.84";
const char kProxyIPAddressLocal[] = "192.23.34.1";
const char kProxyIPv6AddressLocal[] = "::ffff:192.23.34.1";
const char kProxyURLRemote[] = "http://www.google.com";
const int kProxyFD = 100;
const short kProxyPortLocal = 5540;
const short kProxyPortRemote = 80;
const int kSocketError = 3000;
} // namespace {}
MATCHER_P(IsSameIPAddress, ip_addr, "") {
return arg.Equals(ip_addr);
}
class ConnectionHealthCheckerTest : public Test {
public:
ConnectionHealthCheckerTest()
: interface_name_(kInterfaceName),
device_info_(&control_, &dispatcher_,
reinterpret_cast<Metrics*>(NULL),
reinterpret_cast<Manager*>(NULL)),
connection_(new NiceMock<MockConnection>(&device_info_)),
socket_(NULL) {}
// Invokes
int GetSockName(int fd, struct sockaddr *addr_out, socklen_t *sockaddr_size) {
struct sockaddr_in addr;
EXPECT_EQ(kProxyFD, fd);
EXPECT_LE(sizeof(sockaddr_in), *sockaddr_size);
addr.sin_family = AF_INET;
inet_pton(AF_INET, kProxyIPAddressLocal, &addr.sin_addr);
addr.sin_port = htons(kProxyPortLocal);
memcpy(addr_out, &addr, sizeof(addr));
*sockaddr_size = sizeof(sockaddr_in);
return 0;
}
int GetSockNameReturnsIPv6(int fd, struct sockaddr *addr_out,
socklen_t *sockaddr_size) {
struct sockaddr_in6 addr;
EXPECT_EQ(kProxyFD, fd);
EXPECT_LE(sizeof(sockaddr_in6), *sockaddr_size);
addr.sin6_family = AF_INET6;
inet_pton(AF_INET6, kProxyIPv6AddressLocal, &addr.sin6_addr);
addr.sin6_port = htons(kProxyPortLocal);
memcpy(addr_out, &addr, sizeof(addr));
*sockaddr_size = sizeof(sockaddr_in6);
return 0;
}
void InvokeOnConnectionComplete(bool success, int sock_fd) {
health_checker_->OnConnectionComplete(success, sock_fd);
}
void InvokeGetDNSResultFailure() {
Error error(Error::kOperationFailed, "");
IPAddress address(IPAddress::kFamilyUnknown);
health_checker_->GetDNSResult(error, address);
}
void InvokeGetDNSResultSuccess(const IPAddress &address) {
Error error;
health_checker_->GetDNSResult(error, address);
}
protected:
void SetUp() {
EXPECT_CALL(*connection_.get(), interface_name())
.WillRepeatedly(ReturnRef(interface_name_));
ON_CALL(*connection_.get(), dns_servers())
.WillByDefault(ReturnRef(dns_servers_));
// ConnectionHealthChecker constructor should add some IPs
EXPECT_CALL(remote_ips_, AddUnique(_)).Times(AtLeast(1));
health_checker_.reset(
new ConnectionHealthChecker(
connection_,
&dispatcher_,
&remote_ips_,
Bind(&ConnectionHealthCheckerTest::ResultCallbackTarget,
Unretained(this))));
Mock::VerifyAndClearExpectations(&remote_ips_);
socket_ = new StrictMock<MockSockets>();
tcp_connection_ = new StrictMock<MockAsyncConnection>();
socket_info_reader_ = new StrictMock<MockSocketInfoReader>();
// Passes ownership for all of these.
health_checker_->socket_.reset(socket_);
health_checker_->tcp_connection_.reset(tcp_connection_);
health_checker_->socket_info_reader_.reset(socket_info_reader_);
health_checker_->dns_client_factory_ = MockDNSClientFactory::GetInstance();
}
void TearDown() {
ExpectStop();
}
// Accessors for private data in ConnectionHealthChecker.
const Sockets *socket() {
return health_checker_->socket_.get();
}
const AsyncConnection *tcp_connection() {
return health_checker_->tcp_connection_.get();
}
ScopedVector<DNSClient> &dns_clients() {
return health_checker_->dns_clients_;
}
int NumDNSQueries() {
return ConnectionHealthChecker::kNumDNSQueries;
}
int MaxFailedConnectionAttempts() {
return ConnectionHealthChecker::kMaxFailedConnectionAttempts;
}
int MaxSentDataPollingAttempts() {
return ConnectionHealthChecker::kMaxSentDataPollingAttempts;
}
int MinCongestedQueueAttempts() {
return ConnectionHealthChecker::kMinCongestedQueueAttempts;
}
int MinSuccessfulSendAttempts() {
return ConnectionHealthChecker::kMinSuccessfulSendAttempts;
}
void SetTCPStateUpdateWaitMilliseconds(int new_wait) {
health_checker_->tcp_state_update_wait_milliseconds_ = new_wait;
}
// Mock Callbacks
MOCK_METHOD1(ResultCallbackTarget,
void(ConnectionHealthChecker::Result result));
// Helper methods
IPAddress StringToIPv4Address(const string &address_string) {
IPAddress ip_address(IPAddress::kFamilyIPv4);
EXPECT_TRUE(ip_address.SetAddressFromString(address_string));
return ip_address;
}
// Naming: CreateSocketInfo
// + (Proxy/Other) : TCP connection for proxy socket / some other
// socket.
// + arg1: Pass in any SocketInfo::ConnectionState you want.
// + arg2: Pass in any value of transmit_queue_value you want.
SocketInfo CreateSocketInfoOther() {
return SocketInfo(
SocketInfo::kConnectionStateUnknown,
StringToIPv4Address(kIPAddress_8_8_8_8),
0,
StringToIPv4Address(kProxyIPAddressRemote),
kProxyPortRemote,
0,
0,
SocketInfo::kTimerStateUnknown);
}
SocketInfo CreateSocketInfoProxy(SocketInfo::ConnectionState state) {
return SocketInfo(
state,
StringToIPv4Address(kProxyIPAddressLocal),
kProxyPortLocal,
StringToIPv4Address(kProxyIPAddressRemote),
kProxyPortRemote,
0,
0,
SocketInfo::kTimerStateUnknown);
}
SocketInfo CreateSocketInfoProxy(SocketInfo::ConnectionState state,
SocketInfo::TimerState timer_state,
uint64 transmit_queue_value) {
return SocketInfo(
state,
StringToIPv4Address(kProxyIPAddressLocal),
kProxyPortLocal,
StringToIPv4Address(kProxyIPAddressRemote),
kProxyPortRemote,
transmit_queue_value,
0,
timer_state);
}
// Expectations
void ExpectReset() {
EXPECT_EQ(connection_.get(), health_checker_->connection_.get());
EXPECT_EQ(&dispatcher_, health_checker_->dispatcher_);
EXPECT_EQ(socket_, health_checker_->socket_.get());
EXPECT_FALSE(socket_ == NULL);
EXPECT_EQ(socket_info_reader_, health_checker_->socket_info_reader_.get());
EXPECT_FALSE(socket_info_reader_ == NULL);
EXPECT_FALSE(health_checker_->connection_complete_callback_.is_null());
EXPECT_EQ(tcp_connection_, health_checker_->tcp_connection_.get());
EXPECT_FALSE(tcp_connection_ == NULL);
EXPECT_FALSE(health_checker_->health_check_in_progress_);
}
// Setup ConnectionHealthChecker::GetSocketInfo to return sock_info.
// This only works if GetSocketInfo is called with kProxyFD.
// If no matching sock_info is provided (Does not belong to proxy socket),
// GetSocketInfo will (correctly) return false.
void ExpectGetSocketInfoReturns(SocketInfo sock_info) {
vector<SocketInfo> info_list;
info_list.push_back(sock_info);
EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
.InSequence(seq_)
.WillOnce(Invoke(this,
&ConnectionHealthCheckerTest::GetSockName));
EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
.InSequence(seq_)
.WillOnce(DoAll(SetArgumentPointee<0>(info_list),
Return(true)));
}
void ExpectSuccessfulStart() {
EXPECT_CALL(remote_ips_, Empty()).WillRepeatedly(Return(false));
EXPECT_CALL(remote_ips_, GetRandomIP())
.WillRepeatedly(Return(StringToIPv4Address(kProxyIPAddressRemote)));
EXPECT_CALL(
*tcp_connection_,
Start(IsSameIPAddress(StringToIPv4Address(kProxyIPAddressRemote)),
kProxyPortRemote))
.InSequence(seq_)
.WillOnce(Return(true));
}
void ExpectRetry() {
EXPECT_CALL(*socket_, Close(kProxyFD))
.InSequence(seq_);
EXPECT_CALL(
*tcp_connection_,
Start(IsSameIPAddress(StringToIPv4Address(kProxyIPAddressRemote)),
kProxyPortRemote))
.InSequence(seq_)
.WillOnce(Return(true));
}
void ExpectStop() {
if (tcp_connection_)
EXPECT_CALL(*tcp_connection_, Stop())
.InSequence(seq_);
}
void ExpectCleanUp() {
EXPECT_CALL(*socket_, Close(kProxyFD))
.InSequence(seq_);
EXPECT_CALL(*tcp_connection_, Stop())
.InSequence(seq_);
}
void VerifyAndClearAllExpectations() {
Mock::VerifyAndClearExpectations(this);
Mock::VerifyAndClearExpectations(tcp_connection_);
Mock::VerifyAndClearExpectations(socket_);
Mock::VerifyAndClearExpectations(socket_info_reader_);
}
// Needed for other mocks, but not for the tests directly.
const string interface_name_;
NiceMock<MockControl> control_;
NiceMock<MockDeviceInfo> device_info_;
vector<string> dns_servers_;
scoped_refptr<NiceMock<MockConnection> > connection_;
EventDispatcher dispatcher_;
MockIPAddressStore remote_ips_;
StrictMock<MockSockets> *socket_;
StrictMock<MockSocketInfoReader> *socket_info_reader_;
StrictMock<MockAsyncConnection> *tcp_connection_;
// Expectations in the Expect* functions are put in this sequence.
// This allows us to chain calls to Expect* functions.
Sequence seq_;
scoped_ptr<ConnectionHealthChecker> health_checker_;
};
TEST_F(ConnectionHealthCheckerTest, Constructor) {
ExpectReset();
}
TEST_F(ConnectionHealthCheckerTest, SetConnection) {
scoped_refptr<NiceMock<MockConnection> > new_connection
= new NiceMock<MockConnection>(&device_info_);
// If a health check was in progress when SetConnection is called, verify
// that it restarts with the new connection.
ExpectSuccessfulStart();
health_checker_->Start();
VerifyAndClearAllExpectations();
EXPECT_CALL(remote_ips_, Empty()).WillRepeatedly(Return(true));
EXPECT_CALL(*new_connection.get(), interface_name())
.WillRepeatedly(ReturnRef(interface_name_));
EXPECT_CALL(*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultUnknown));
health_checker_->SetConnection(new_connection);
EXPECT_NE(tcp_connection_, health_checker_->tcp_connection());
EXPECT_EQ(new_connection.get(), health_checker_->connection());
// health_checker_ has reset tcp_connection_ to a new object.
// Since it owned tcp_connection_, the object has been destroyed.
tcp_connection_ = NULL;
}
TEST_F(ConnectionHealthCheckerTest, GarbageCollectDNSClients) {
dns_clients().clear();
health_checker_->GarbageCollectDNSClients();
EXPECT_TRUE(dns_clients().empty());
for (int i = 0; i < 3; ++i) {
MockDNSClient *dns_client = new MockDNSClient();
EXPECT_CALL(*dns_client, IsActive())
.WillOnce(Return(true))
.WillOnce(Return(true))
.WillOnce(Return(false));
// Takes ownership.
dns_clients().push_back(dns_client);
}
for (int i = 0; i < 2; ++i) {
MockDNSClient *dns_client = new MockDNSClient();
EXPECT_CALL(*dns_client, IsActive())
.WillOnce(Return(false));
// Takes ownership.
dns_clients().push_back(dns_client);
}
EXPECT_EQ(5, dns_clients().size());
health_checker_->GarbageCollectDNSClients();
EXPECT_EQ(3, dns_clients().size());
health_checker_->GarbageCollectDNSClients();
EXPECT_EQ(3, dns_clients().size());
health_checker_->GarbageCollectDNSClients();
EXPECT_TRUE(dns_clients().empty());
}
TEST_F(ConnectionHealthCheckerTest, AddRemoteURL) {
HTTPURL url;
url.ParseFromString(kProxyURLRemote);
string host = url.host();
IPAddress remote_ip = StringToIPv4Address(kProxyIPAddressRemote);
IPAddress remote_ip_2 = StringToIPv4Address(kIPAddress_8_8_8_8);
MockDNSClientFactory *dns_client_factory
= MockDNSClientFactory::GetInstance();
vector<MockDNSClient *> dns_client_buffer;
// All DNS queries fail.
for (int i = 0; i < NumDNSQueries(); ++i) {
MockDNSClient *dns_client = new MockDNSClient();
EXPECT_CALL(*dns_client, Start(host, _))
.WillOnce(Return(false));
dns_client_buffer.push_back(dns_client);
}
// Will pass ownership of dns_clients elements.
for (int i = 0; i < NumDNSQueries(); ++i) {
EXPECT_CALL(*dns_client_factory, CreateDNSClient(_,_,_,_,_,_))
.InSequence(seq_)
.WillOnce(Return(dns_client_buffer[i]));
}
EXPECT_CALL(remote_ips_, AddUnique(_)).Times(0);
health_checker_->AddRemoteURL(kProxyURLRemote);
Mock::VerifyAndClearExpectations(dns_client_factory);
Mock::VerifyAndClearExpectations(&remote_ips_);
dns_client_buffer.clear();
dns_clients().clear();
// All but one DNS queries fail, 1 succeeds.
for (int i = 0; i < NumDNSQueries(); ++i) {
MockDNSClient *dns_client = new MockDNSClient();
EXPECT_CALL(*dns_client, Start(host, _))
.WillOnce(Return(true));
dns_client_buffer.push_back(dns_client);
}
// Will pass ownership of dns_clients elements.
for (int i = 0; i < NumDNSQueries(); ++i) {
EXPECT_CALL(*dns_client_factory, CreateDNSClient(_,_,_,_,_,_))
.InSequence(seq_)
.WillOnce(Return(dns_client_buffer[i]));
}
EXPECT_CALL(remote_ips_, AddUnique(_));
health_checker_->AddRemoteURL(kProxyURLRemote);
for(int i = 0; i < NumDNSQueries() - 1; ++i) {
InvokeGetDNSResultFailure();
}
InvokeGetDNSResultSuccess(remote_ip);
Mock::VerifyAndClearExpectations(dns_client_factory);
Mock::VerifyAndClearExpectations(&remote_ips_);
dns_client_buffer.clear();
dns_clients().clear();
// Only 2 distinct IP addresses are returned.
for (int i = 0; i < NumDNSQueries(); ++i) {
MockDNSClient *dns_client = new MockDNSClient();
EXPECT_CALL(*dns_client, Start(host, _))
.WillOnce(Return(true));
dns_client_buffer.push_back(dns_client);
}
// Will pass ownership of dns_clients elements.
for (int i = 0; i < NumDNSQueries(); ++i) {
EXPECT_CALL(*dns_client_factory, CreateDNSClient(_,_,_,_,_,_))
.InSequence(seq_)
.WillOnce(Return(dns_client_buffer[i]));
}
EXPECT_CALL(remote_ips_, AddUnique(IsSameIPAddress(remote_ip))).Times(4);
EXPECT_CALL(remote_ips_, AddUnique(IsSameIPAddress(remote_ip_2)));
health_checker_->AddRemoteURL(kProxyURLRemote);
for (int i = 0; i < NumDNSQueries() - 1; ++i) {
InvokeGetDNSResultSuccess(remote_ip);
}
InvokeGetDNSResultSuccess(remote_ip_2);
Mock::VerifyAndClearExpectations(dns_client_factory);
Mock::VerifyAndClearExpectations(&remote_ips_);
dns_client_buffer.clear();
dns_clients().clear();
}
TEST_F(ConnectionHealthCheckerTest, GetSocketInfo) {
SocketInfo sock_info;
vector<SocketInfo> info_list;
// GetSockName fails.
EXPECT_CALL(*socket_, GetSockName(_,_,_))
.WillOnce(Return(-1));
EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
Mock::VerifyAndClearExpectations(socket_);
// GetSockName returns IPv6.
EXPECT_CALL(*socket_, GetSockName(_,_,_))
.WillOnce(
Invoke(this,
&ConnectionHealthCheckerTest::GetSockNameReturnsIPv6));
EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
Mock::VerifyAndClearExpectations(socket_);
// LoadTcpSocketInfo fails.
EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
.WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
.WillOnce(Return(false));
EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
Mock::VerifyAndClearExpectations(socket_);
Mock::VerifyAndClearExpectations(socket_info_reader_);
// LoadTcpSocketInfo returns empty list.
info_list.clear();
EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
.WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
.WillOnce(DoAll(SetArgumentPointee<0>(info_list),
Return(true)));
EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
Mock::VerifyAndClearExpectations(socket_);
Mock::VerifyAndClearExpectations(socket_info_reader_);
// LoadTcpSocketInfo returns a list without our socket.
info_list.clear();
info_list.push_back(CreateSocketInfoOther());
EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
.WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
.WillOnce(DoAll(SetArgumentPointee<0>(info_list),
Return(true)));
EXPECT_FALSE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
Mock::VerifyAndClearExpectations(socket_);
Mock::VerifyAndClearExpectations(socket_info_reader_);
// LoadTcpSocketInfo returns a list with only our socket.
info_list.clear();
info_list.push_back(
CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown));
EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
.WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
.WillOnce(DoAll(SetArgumentPointee<0>(info_list),
Return(true)));
EXPECT_TRUE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
EXPECT_TRUE(CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown)
.IsSameSocketAs(sock_info));
Mock::VerifyAndClearExpectations(socket_);
Mock::VerifyAndClearExpectations(socket_info_reader_);
// LoadTcpSocketInfo returns a list with two sockets, including ours.
info_list.clear();
info_list.push_back(CreateSocketInfoOther());
info_list.push_back(
CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown));
EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
.WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
.WillOnce(DoAll(SetArgumentPointee<0>(info_list),
Return(true)));
EXPECT_TRUE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
EXPECT_TRUE(CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown)
.IsSameSocketAs(sock_info));
Mock::VerifyAndClearExpectations(socket_);
Mock::VerifyAndClearExpectations(socket_info_reader_);
info_list.clear();
info_list.push_back(
CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown));
info_list.push_back(CreateSocketInfoOther());
EXPECT_CALL(*socket_, GetSockName(kProxyFD, _, _))
.WillOnce(Invoke(this, &ConnectionHealthCheckerTest::GetSockName));
EXPECT_CALL(*socket_info_reader_, LoadTcpSocketInfo(_))
.WillOnce(DoAll(SetArgumentPointee<0>(info_list),
Return(true)));
EXPECT_TRUE(health_checker_->GetSocketInfo(kProxyFD, &sock_info));
EXPECT_TRUE(CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown)
.IsSameSocketAs(sock_info));
Mock::VerifyAndClearExpectations(socket_);
Mock::VerifyAndClearExpectations(socket_info_reader_);
}
TEST_F(ConnectionHealthCheckerTest, NextHealthCheckSample) {
IPAddress ip = StringToIPv4Address(kProxyIPAddressRemote);
ON_CALL(remote_ips_, GetRandomIP())
.WillByDefault(Return(ip));
health_checker_->set_num_connection_failures(MaxFailedConnectionAttempts());
ExpectStop();
EXPECT_CALL(
*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultConnectionFailure));
health_checker_->NextHealthCheckSample();
dispatcher_.DispatchPendingEvents();
VerifyAndClearAllExpectations();
health_checker_->set_num_congested_queue_detected(
MinCongestedQueueAttempts());
ExpectStop();
EXPECT_CALL(
*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultCongestedTxQueue));
health_checker_->NextHealthCheckSample();
dispatcher_.DispatchPendingEvents();
VerifyAndClearAllExpectations();
health_checker_->set_num_successful_sends(MinSuccessfulSendAttempts());
ExpectStop();
EXPECT_CALL(
*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultSuccess));
health_checker_->NextHealthCheckSample();
dispatcher_.DispatchPendingEvents();
VerifyAndClearAllExpectations();
EXPECT_CALL(*tcp_connection_, Start(_,_)).WillOnce(Return(true));
health_checker_->NextHealthCheckSample();
VerifyAndClearAllExpectations();
// This test assumes that there are at least 2 connection attempts left
// before ConnectionHealthChecker gives up.
EXPECT_CALL(*tcp_connection_, Start(_,_))
.WillOnce(Return(false))
.WillOnce(Return(true));
short num_connection_failures = health_checker_->num_connection_failures();
health_checker_->NextHealthCheckSample();
EXPECT_EQ(num_connection_failures + 1,
health_checker_->num_connection_failures());
}
TEST_F(ConnectionHealthCheckerTest, OnConnectionComplete) {
// Test that num_connection_attempts is incremented on failure when
// (1) Async Connection fails.
health_checker_->set_num_connection_failures(
MaxFailedConnectionAttempts() - 1);
ExpectStop();
EXPECT_CALL(
*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultConnectionFailure));
health_checker_->OnConnectionComplete(false, -1);
dispatcher_.DispatchPendingEvents();
VerifyAndClearAllExpectations();
// (2) The connection state is garbled up.
health_checker_->set_num_connection_failures(
MaxFailedConnectionAttempts() - 1);
ExpectGetSocketInfoReturns(
CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown));
EXPECT_CALL(*socket_, Close(kProxyFD));
ExpectStop();
EXPECT_CALL(
*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultConnectionFailure));
health_checker_->OnConnectionComplete(true, kProxyFD);
dispatcher_.DispatchPendingEvents();
VerifyAndClearAllExpectations();
// (3) Send fails.
health_checker_->set_num_connection_failures(
MaxFailedConnectionAttempts() - 1);
ExpectGetSocketInfoReturns(
CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished));
EXPECT_CALL(*socket_, Send(kProxyFD, _, Gt(0), _)).WillOnce(Return(-1));
EXPECT_CALL(*socket_, Error()).WillOnce(Return(kSocketError));
EXPECT_CALL(*socket_, Close(kProxyFD));
ExpectStop();
EXPECT_CALL(
*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultConnectionFailure));
health_checker_->OnConnectionComplete(true, kProxyFD);
dispatcher_.DispatchPendingEvents();
}
TEST_F(ConnectionHealthCheckerTest, VerifySentData) {
// (1) Test that num_connection_attempts is incremented when the connection
// state is garbled up.
health_checker_->set_num_connection_failures(
MaxFailedConnectionAttempts() - 1);
ExpectGetSocketInfoReturns(
CreateSocketInfoProxy(SocketInfo::kConnectionStateUnknown));
EXPECT_CALL(*socket_, Close(kProxyFD));
ExpectStop();
EXPECT_CALL(
*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultConnectionFailure));
health_checker_->set_sock_fd(kProxyFD);
health_checker_->VerifySentData();
dispatcher_.DispatchPendingEvents();
VerifyAndClearAllExpectations();
// (2) Test that num_congested_queue_detected is incremented when all polling
// attempts have expired.
health_checker_->set_num_congested_queue_detected(
MinCongestedQueueAttempts() - 1);
health_checker_->set_num_tx_queue_polling_attempts(
MaxSentDataPollingAttempts());
health_checker_->set_old_transmit_queue_value(0);
ExpectGetSocketInfoReturns(
CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished,
SocketInfo::kTimerStateRetransmitTimerPending,
1));
EXPECT_CALL(*socket_, Close(kProxyFD));
ExpectStop();
EXPECT_CALL(
*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultCongestedTxQueue));
health_checker_->set_sock_fd(kProxyFD);
health_checker_->VerifySentData();
dispatcher_.DispatchPendingEvents();
VerifyAndClearAllExpectations();
// (3) Test that num_successful_sends is incremented if everything goes fine.
health_checker_->set_num_successful_sends(MinSuccessfulSendAttempts() - 1);
health_checker_->set_old_transmit_queue_value(0);
ExpectGetSocketInfoReturns(
CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished,
SocketInfo::kTimerStateNoTimerPending,
0));
EXPECT_CALL(*socket_, Close(kProxyFD));
ExpectStop();
EXPECT_CALL(
*this,
ResultCallbackTarget(ConnectionHealthChecker::kResultSuccess));
health_checker_->set_sock_fd(kProxyFD);
health_checker_->VerifySentData();
dispatcher_.DispatchPendingEvents();
VerifyAndClearAllExpectations();
// (4) Test that VerifySentData correctly polls the tcpinfo twice.
// We want to immediately dispatch posted tasks.
SetTCPStateUpdateWaitMilliseconds(0);
health_checker_->set_num_congested_queue_detected(
MinCongestedQueueAttempts() - 1);
health_checker_->set_num_tx_queue_polling_attempts(
MaxSentDataPollingAttempts() - 1);
health_checker_->set_old_transmit_queue_value(0);
ExpectGetSocketInfoReturns(
CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished,
SocketInfo::kTimerStateRetransmitTimerPending,
1));
ExpectGetSocketInfoReturns(
CreateSocketInfoProxy(SocketInfo::kConnectionStateEstablished,
SocketInfo::kTimerStateRetransmitTimerPending,
1));
EXPECT_CALL(*socket_, Close(kProxyFD));
ExpectStop();
EXPECT_CALL(
*this, ResultCallbackTarget(
ConnectionHealthChecker::kResultCongestedTxQueue))
.InSequence(seq_);
health_checker_->set_sock_fd(kProxyFD);
health_checker_->VerifySentData();
dispatcher_.DispatchPendingEvents();
dispatcher_.DispatchPendingEvents();
// Force an extra dispatch to make sure that VerifySentData did not poll an
// extra time. This dispatch should be a no-op.
dispatcher_.DispatchPendingEvents();
VerifyAndClearAllExpectations();
}
// Flow: Start() -> Start()
// Expectation: Only one AsyncConnection is setup
TEST_F(ConnectionHealthCheckerTest, StartStartSkipsSecond) {
EXPECT_CALL(*tcp_connection_, Start(_,_))
.WillOnce(Return(true));
EXPECT_CALL(remote_ips_, Empty()).WillRepeatedly(Return(false));
EXPECT_CALL(remote_ips_, GetRandomIP())
.WillOnce(Return(StringToIPv4Address(kProxyIPAddressRemote)));
health_checker_->Start();
health_checker_->Start();
}
// Precondition: size(|remote_ips_|) > 0
// Flow: Start() -> Stop() before ConnectionComplete()
// Expectation: No call to |result_callback|
TEST_F(ConnectionHealthCheckerTest, StartStopNoCallback) {
EXPECT_CALL(*tcp_connection_, Start(_,_))
.WillOnce(Return(true));
EXPECT_CALL(*tcp_connection_, Stop());
EXPECT_CALL(*this, ResultCallbackTarget(_))
.Times(0);
EXPECT_CALL(remote_ips_, Empty()).WillRepeatedly(Return(false));
EXPECT_CALL(remote_ips_, GetRandomIP())
.WillOnce(Return(StringToIPv4Address(kProxyIPAddressRemote)));
health_checker_->Start();
health_checker_->Stop();
}
// Precondition: Empty remote_ips_
// Flow: Start()
// Expectation: call |result_callback| with kResultUnknown
TEST_F(ConnectionHealthCheckerTest, StartImmediateFailure) {
EXPECT_CALL(remote_ips_, Empty()).WillOnce(Return(true));
EXPECT_CALL(*tcp_connection_, Stop());
EXPECT_CALL(*this, ResultCallbackTarget(
ConnectionHealthChecker::kResultUnknown));
health_checker_->Start();
Mock::VerifyAndClearExpectations(this);
Mock::VerifyAndClearExpectations(&remote_ips_);
Mock::VerifyAndClearExpectations(tcp_connection_);
EXPECT_CALL(remote_ips_, Empty()).WillRepeatedly(Return(false));
EXPECT_CALL(remote_ips_, GetRandomIP())
.WillRepeatedly(Return(StringToIPv4Address(kProxyIPAddressRemote)));
EXPECT_CALL(*tcp_connection_,
Start(IsSameIPAddress(StringToIPv4Address(kProxyIPAddressRemote)),
kProxyPortRemote))
.Times(MaxFailedConnectionAttempts())
.WillRepeatedly(Return(false));
EXPECT_CALL(*tcp_connection_, Stop());
EXPECT_CALL(*this, ResultCallbackTarget(
ConnectionHealthChecker::kResultConnectionFailure));
health_checker_->Start();
dispatcher_.DispatchPendingEvents();
Mock::VerifyAndClearExpectations(this);
Mock::VerifyAndClearExpectations(tcp_connection_);
}
} // namespace shill