| Index: google_apis/gcm/engine/connection_factory_impl_unittest.cc
|
| diff --git a/google_apis/gcm/engine/connection_factory_impl_unittest.cc b/google_apis/gcm/engine/connection_factory_impl_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..40adcf2783285f3a0f09d90e73aa972a2f96a87a
|
| --- /dev/null
|
| +++ b/google_apis/gcm/engine/connection_factory_impl_unittest.cc
|
| @@ -0,0 +1,295 @@
|
| +// 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, BuildConnectionHandler) {
|
| + EXPECT_FALSE(factory()->IsEndpointReachable());
|
| + ConnectionHandler* handler = factory()->BuildConnectionHandler(
|
| + base::Bind(&ReadContinuation),
|
| + base::Bind(&WriteContinuation));
|
| + ASSERT_TRUE(handler);
|
| + EXPECT_FALSE(factory()->IsEndpointReachable());
|
| +}
|
| +
|
| +// An initial successful connection should not result in backoff.
|
| +TEST_F(ConnectionFactoryImplTest, ConnectSuccess) {
|
| + factory()->BuildConnectionHandler(
|
| + ConnectionHandler::ProtoReceivedCallback(),
|
| + ConnectionHandler::ProtoSentCallback());
|
| + factory()->SetConnectResult(net::OK);
|
| + factory()->Connect(mcs_proto::LoginRequest());
|
| + EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
|
| +}
|
| +
|
| +// A connection failure should result in backoff.
|
| +TEST_F(ConnectionFactoryImplTest, ConnectFail) {
|
| + factory()->BuildConnectionHandler(
|
| + ConnectionHandler::ProtoReceivedCallback(),
|
| + ConnectionHandler::ProtoSentCallback());
|
| + factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
|
| + factory()->Connect(mcs_proto::LoginRequest());
|
| + EXPECT_FALSE(factory()->NextRetryAttempt().is_null());
|
| +}
|
| +
|
| +// A connection success after a failure should reset backoff.
|
| +TEST_F(ConnectionFactoryImplTest, FailThenSucceed) {
|
| + factory()->BuildConnectionHandler(
|
| + ConnectionHandler::ProtoReceivedCallback(),
|
| + ConnectionHandler::ProtoSentCallback());
|
| + factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
|
| + base::TimeTicks connect_time = base::TimeTicks::Now();
|
| + factory()->Connect(mcs_proto::LoginRequest());
|
| + 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()->BuildConnectionHandler(
|
| + ConnectionHandler::ProtoReceivedCallback(),
|
| + ConnectionHandler::ProtoSentCallback());
|
| +
|
| + const int kNumAttempts = 5;
|
| + factory()->SetMultipleConnectResults(net::ERR_CONNECTION_FAILED,
|
| + kNumAttempts);
|
| +
|
| + base::TimeTicks connect_time = base::TimeTicks::Now();
|
| + factory()->Connect(mcs_proto::LoginRequest());
|
| + 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()->BuildConnectionHandler(
|
| + ConnectionHandler::ProtoReceivedCallback(),
|
| + ConnectionHandler::ProtoSentCallback());
|
| + factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
|
| + factory()->Connect(mcs_proto::LoginRequest());
|
| + 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()->BuildConnectionHandler(
|
| + ConnectionHandler::ProtoReceivedCallback(),
|
| + ConnectionHandler::ProtoSentCallback());
|
| + factory()->SetConnectResult(net::ERR_CONNECTION_FAILED);
|
| + factory()->Connect(mcs_proto::LoginRequest());
|
| + WaitForConnections();
|
| + EXPECT_FALSE(factory()->NextRetryAttempt().is_null());
|
| +
|
| + factory()->OnConnectionTypeChanged(
|
| + net::NetworkChangeNotifier::CONNECTION_WIFI);
|
| + EXPECT_TRUE(factory()->NextRetryAttempt().is_null());
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace gcm
|
|
|