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 |