blob: 1e0ccefef26ed648f52ba5319247495a8eb7c142 [file] [log] [blame]
// Copyright (c) 2013 The Chromium 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 "google_apis/gcm/engine/connection_factory_impl.h"
#include <cmath>
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/time/time.h"
#include "net/base/backoff_entry.h"
#include "net/http/http_network_session.h"
#include "testing/gtest/include/gtest/gtest.h"
class Policy;
namespace gcm {
namespace {
const char kMCSEndpoint[] = "http://my.server";
const int kBackoffDelayMs = 1;
const int kBackoffMultiplier = 2;
// A backoff policy with small enough delays that tests aren't burdened.
const net::BackoffEntry::Policy kTestBackoffPolicy = {
// Number of initial errors (in sequence) to ignore before applying
// exponential back-off rules.
0,
// Initial delay for exponential back-off in ms.
kBackoffDelayMs,
// Factor by which the waiting time will be multiplied.
kBackoffMultiplier,
// Fuzzing percentage. ex: 10% will spread requests randomly
// between 90%-100% of the calculated time.
0,
// Maximum amount of time we are willing to delay our request in ms.
10,
// Time to keep an entry from being discarded even when it
// has no significant state, -1 to never discard.
-1,
// Don't use initial delay unless the last request was an error.
false,
};
// Helper for calculating total expected exponential backoff delay given an
// arbitrary number of failed attempts. See BackoffEntry::CalculateReleaseTime.
double CalculateBackoff(int num_attempts) {
double delay = kBackoffDelayMs;
for (int i = 1; i < num_attempts; ++i) {
delay += kBackoffDelayMs * pow(static_cast<double>(kBackoffMultiplier),
i - 1);
}
DVLOG(1) << "Expected backoff " << delay << " milliseconds.";
return delay;
}
// Helper methods that should never actually be called due to real connections
// being stubbed out.
void ReadContinuation(
scoped_ptr<google::protobuf::MessageLite> message) {
ADD_FAILURE();
}
void WriteContinuation() {
ADD_FAILURE();
}
// A connection factory that stubs out network requests and overrides the
// backoff policy.
class TestConnectionFactoryImpl : public ConnectionFactoryImpl {
public:
TestConnectionFactoryImpl(const base::Closure& finished_callback);
virtual ~TestConnectionFactoryImpl();
// Overridden stubs.
virtual void ConnectImpl() OVERRIDE;
virtual void InitHandler() OVERRIDE;
virtual scoped_ptr<net::BackoffEntry> CreateBackoffEntry(
const net::BackoffEntry::Policy* const policy) OVERRIDE;
// Helpers for verifying connection attempts are made. Connection results
// must be consumed.
void SetConnectResult(int connect_result);
void SetMultipleConnectResults(int connect_result, int num_expected_attempts);
private:
// The result to return on the next connect attempt.
int connect_result_;
// The number of expected connection attempts;
int num_expected_attempts_;
// Whether all expected connection attempts have been fulfilled since an
// expectation was last set.
bool connections_fulfilled_;
// Callback to invoke when all connection attempts have been made.
base::Closure finished_callback_;
};
TestConnectionFactoryImpl::TestConnectionFactoryImpl(
const base::Closure& finished_callback)
: ConnectionFactoryImpl(GURL(kMCSEndpoint), NULL, NULL),
connect_result_(net::ERR_UNEXPECTED),
num_expected_attempts_(0),
connections_fulfilled_(true),
finished_callback_(finished_callback) {
}
TestConnectionFactoryImpl::~TestConnectionFactoryImpl() {
EXPECT_EQ(0, num_expected_attempts_);
}
void TestConnectionFactoryImpl::ConnectImpl() {
ASSERT_GT(num_expected_attempts_, 0);
OnConnectDone(connect_result_);
--num_expected_attempts_;
if (num_expected_attempts_ == 0) {
connect_result_ = net::ERR_UNEXPECTED;
connections_fulfilled_ = true;
finished_callback_.Run();
}
}
void TestConnectionFactoryImpl::InitHandler() {
EXPECT_NE(connect_result_, net::ERR_UNEXPECTED);
}
scoped_ptr<net::BackoffEntry> TestConnectionFactoryImpl::CreateBackoffEntry(
const net::BackoffEntry::Policy* const policy) {
return scoped_ptr<net::BackoffEntry>(
new net::BackoffEntry(&kTestBackoffPolicy));
}
void TestConnectionFactoryImpl::SetConnectResult(int connect_result) {
DCHECK_NE(connect_result, net::ERR_UNEXPECTED);
ASSERT_EQ(0, num_expected_attempts_);
connections_fulfilled_ = false;
connect_result_ = connect_result;
num_expected_attempts_ = 1;
}
void TestConnectionFactoryImpl::SetMultipleConnectResults(
int connect_result,
int num_expected_attempts) {
DCHECK_NE(connect_result, net::ERR_UNEXPECTED);
DCHECK_GT(num_expected_attempts, 0);
ASSERT_EQ(0, num_expected_attempts_);
connections_fulfilled_ = false;
connect_result_ = connect_result;
num_expected_attempts_ = num_expected_attempts;
}
class ConnectionFactoryImplTest : public testing::Test {
public:
ConnectionFactoryImplTest();
virtual ~ConnectionFactoryImplTest();
TestConnectionFactoryImpl* factory() { return &factory_; }
void WaitForConnections();
private:
void ConnectionsComplete();
TestConnectionFactoryImpl factory_;
base::MessageLoop message_loop_;
scoped_ptr<base::RunLoop> run_loop_;
};
ConnectionFactoryImplTest::ConnectionFactoryImplTest()
: factory_(base::Bind(&ConnectionFactoryImplTest::ConnectionsComplete,
base::Unretained(this))),
run_loop_(new base::RunLoop()) {}
ConnectionFactoryImplTest::~ConnectionFactoryImplTest() {}
void ConnectionFactoryImplTest::WaitForConnections() {
run_loop_->Run();
run_loop_.reset(new base::RunLoop());
}
void ConnectionFactoryImplTest::ConnectionsComplete() {
if (!run_loop_)
return;
run_loop_->Quit();
}
// Verify building a connection handler works.
TEST_F(ConnectionFactoryImplTest, Initialize) {
EXPECT_FALSE(factory()->IsEndpointReachable());
factory()->Initialize(
ConnectionFactory::BuildLoginRequestCallback(),
base::Bind(&ReadContinuation),
base::Bind(&WriteContinuation));
ConnectionHandler* handler = factory()->GetConnectionHandler();
ASSERT_TRUE(handler);
EXPECT_FALSE(factory()->IsEndpointReachable());
}
// An initial successful connection should not result in backoff.
TEST_F(ConnectionFactoryImplTest, ConnectSuccess) {
factory()->Initialize(
ConnectionFactory::BuildLoginRequestCallback(),
ConnectionHandler::ProtoReceivedCallback(),
ConnectionHandler::ProtoSentCallback());
factory()->SetConnectResult(net::OK);
factory()->Connect();
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
}
// A connection failure should result in backoff.
TEST_F(ConnectionFactoryImplTest, ConnectFail) {
factory()->Initialize(
ConnectionFactory::BuildLoginRequestCallback(),
ConnectionHandler::ProtoReceivedCallback(),
ConnectionHandler::ProtoSentCallback());
factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
factory()->Connect();
EXPECT_FALSE(factory()->NextRetryAttempt().is_null());
}
// A connection success after a failure should reset backoff.
TEST_F(ConnectionFactoryImplTest, FailThenSucceed) {
factory()->Initialize(
ConnectionFactory::BuildLoginRequestCallback(),
ConnectionHandler::ProtoReceivedCallback(),
ConnectionHandler::ProtoSentCallback());
factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
base::TimeTicks connect_time = base::TimeTicks::Now();
factory()->Connect();
WaitForConnections();
base::TimeTicks retry_time = factory()->NextRetryAttempt();
EXPECT_FALSE(retry_time.is_null());
EXPECT_GE((retry_time - connect_time).InMilliseconds(), CalculateBackoff(1));
factory()->SetConnectResult(net::OK);
WaitForConnections();
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
}
// Multiple connection failures should retry with an exponentially increasing
// backoff, then reset on success.
TEST_F(ConnectionFactoryImplTest, MultipleFailuresThenSucceed) {
factory()->Initialize(
ConnectionFactory::BuildLoginRequestCallback(),
ConnectionHandler::ProtoReceivedCallback(),
ConnectionHandler::ProtoSentCallback());
const int kNumAttempts = 5;
factory()->SetMultipleConnectResults(net::ERR_CONNECTION_FAILED,
kNumAttempts);
base::TimeTicks connect_time = base::TimeTicks::Now();
factory()->Connect();
WaitForConnections();
base::TimeTicks retry_time = factory()->NextRetryAttempt();
EXPECT_FALSE(retry_time.is_null());
EXPECT_GE((retry_time - connect_time).InMilliseconds(),
CalculateBackoff(kNumAttempts));
factory()->SetConnectResult(net::OK);
WaitForConnections();
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
}
// IP events should reset backoff.
TEST_F(ConnectionFactoryImplTest, FailThenIPEvent) {
factory()->Initialize(
ConnectionFactory::BuildLoginRequestCallback(),
ConnectionHandler::ProtoReceivedCallback(),
ConnectionHandler::ProtoSentCallback());
factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
factory()->Connect();
WaitForConnections();
EXPECT_FALSE(factory()->NextRetryAttempt().is_null());
factory()->OnIPAddressChanged();
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
}
// Connection type events should reset backoff.
TEST_F(ConnectionFactoryImplTest, FailThenConnectionTypeEvent) {
factory()->Initialize(
ConnectionFactory::BuildLoginRequestCallback(),
ConnectionHandler::ProtoReceivedCallback(),
ConnectionHandler::ProtoSentCallback());
factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
factory()->Connect();
WaitForConnections();
EXPECT_FALSE(factory()->NextRetryAttempt().is_null());
factory()->OnConnectionTypeChanged(
net::NetworkChangeNotifier::CONNECTION_WIFI);
EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
}
} // namespace
} // namespace gcm