Index: components/password_manager/core/browser/affiliation_fetch_throttler_unittest.cc |
diff --git a/components/password_manager/core/browser/affiliation_fetch_throttler_unittest.cc b/components/password_manager/core/browser/affiliation_fetch_throttler_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..dfbb314dd68f38dc1f7bd533212de6dffd1039f2 |
--- /dev/null |
+++ b/components/password_manager/core/browser/affiliation_fetch_throttler_unittest.cc |
@@ -0,0 +1,228 @@ |
+// Copyright 2015 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 <queue> |
+ |
+#include "base/callback.h" |
+#include "base/memory/ref_counted.h" |
+#include "base/run_loop.h" |
+#include "base/single_thread_task_runner.h" |
+#include "base/test/test_mock_time_task_runner.h" |
+#include "base/thread_task_runner_handle.h" |
+#include "base/time/tick_clock.h" |
+#include "base/time/time.h" |
+#include "components/password_manager/core/browser/affiliation_fetch_throttler.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace password_manager { |
+namespace { |
+ |
+class MockAffiliationFetchThrottlerDelegate |
+ : public testing::StrictMock<AffiliationFetchThrottlerDelegate> { |
+ public: |
+ MockAffiliationFetchThrottlerDelegate() {} |
+ |
+ MOCK_METHOD0(OnCanSendNetworkRequest, bool()); |
+ |
+ private: |
+ DISALLOW_COPY_AND_ASSIGN(MockAffiliationFetchThrottlerDelegate); |
+}; |
+ |
+class MockNetworkChangeNotifier : public net::NetworkChangeNotifier { |
+ public: |
+ MockNetworkChangeNotifier() : connection_type_(CONNECTION_UNKNOWN) {} |
+ |
+ void set_connection_type(ConnectionType connection_type) { |
+ connection_type_ = connection_type; |
+ NotifyObserversOfConnectionTypeChangeForTests(connection_type_); |
+ } |
+ |
+ ConnectionType GetCurrentConnectionType() const override { |
+ return connection_type_; |
+ } |
+ |
+ private: |
+ ConnectionType connection_type_; |
+}; |
+ |
+} // namespace |
+ |
+class AffiliationFetchThrottlerTest : public testing::Test { |
+ public: |
+ AffiliationFetchThrottlerTest() |
+ : network_change_notifier_(new MockNetworkChangeNotifier), |
+ task_runner_(new base::TestMockTimeTaskRunner) {} |
+ ~AffiliationFetchThrottlerTest() override {} |
+ |
+ void SetUp() override { |
+ policy_.reset(new AffiliationFetchThrottler( |
+ &mock_delegate_, task_runner_, task_runner_->GetMockTickClock())); |
+ } |
+ |
+ void SimulateHasNetworkConnectivity(bool has_connectivity) { |
+ network_change_notifier_->set_connection_type( |
+ has_connectivity ? net::NetworkChangeNotifier::CONNECTION_UNKNOWN |
+ : net::NetworkChangeNotifier::CONNECTION_NONE); |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ // Forwards time, and given that MockAffiliationFetchThrottlerDelegate is a |
+ // strict mock, also ensures OnCanSendNetworkRequest() is not called back |
+ // during this period. |
+ void FastForwardTimeBySecs(double secs) { |
+ task_runner_->FastForwardBy(base::TimeDelta::FromSecondsD(secs)); |
+ } |
+ |
+ void FastForwardUntilNoTasksRemain() { |
+ task_runner_->FastForwardUntilNoTasksRemain(); |
+ } |
+ |
+ size_t NumberOfEnqueuedTasks() { return task_runner_->GetPendingTaskCount(); } |
+ |
+ // Forwards time until the next OnCanSendNetworkRequest() callback, and |
+ // verifies that it occurs between |min_delay_sec| and |max_delay_sec| seconds |
+ // from now, inclusive. |
+ void AssertReleaseBetweenSecs(double min_delay_sec, double max_delay_sec) { |
+ base::TimeTicks ticks_at_start = task_runner_->GetCurrentTime(); |
+ base::TimeDelta min_delay = base::TimeDelta::FromSecondsD(min_delay_sec); |
+ if (min_delay.ToInternalValue() > 0) { |
+ task_runner_->FastForwardBy(min_delay - |
+ base::TimeDelta::FromInternalValue(1)); |
+ } |
+ EXPECT_CALL(mock_delegate_, OnCanSendNetworkRequest()) |
+ .Times(1) |
+ .WillRepeatedly(testing::Return(true)); |
+ task_runner_->FastForwardUntilNoTasksRemain(); |
+ ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate_)); |
+ ASSERT_GE(base::TimeDelta::FromSecondsD(max_delay_sec), |
+ task_runner_->GetCurrentTime() - ticks_at_start); |
+ } |
+ |
+ AffiliationFetchThrottler& policy() { return *policy_.get(); } |
+ |
+ private: |
+ // Needed because NetworkChangeNotifier uses ObserverList, which notifies |
+ // observers on the MessageLoop that belongs to the thread from which they |
+ // have registered. |
+ base::MessageLoop message_loop_; |
+ scoped_ptr<MockNetworkChangeNotifier> network_change_notifier_; |
+ scoped_refptr<base::TestMockTimeTaskRunner> task_runner_; |
+ MockAffiliationFetchThrottlerDelegate mock_delegate_; |
+ scoped_ptr<AffiliationFetchThrottler> policy_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(AffiliationFetchThrottlerTest); |
+}; |
+ |
+TEST_F(AffiliationFetchThrottlerTest, SuccessfulRequests) { |
+ SimulateHasNetworkConnectivity(true); |
+ |
+ policy().SignalNetworkRequestNeeded(); |
+ AssertReleaseBetweenSecs(0, 0); |
+ |
+ // Signal while request is in flight should be ignored. |
+ policy().SignalNetworkRequestNeeded(); |
+ FastForwardUntilNoTasksRemain(); |
+ policy().InformOfNetworkRequestComplete(true); |
+ FastForwardUntilNoTasksRemain(); |
+ |
+ // Duplicate the second signal 3 times: still only 1 callback should arrive. |
+ policy().SignalNetworkRequestNeeded(); |
+ policy().SignalNetworkRequestNeeded(); |
+ policy().SignalNetworkRequestNeeded(); |
+ AssertReleaseBetweenSecs(0, 0); |
+} |
+ |
+TEST_F(AffiliationFetchThrottlerTest, FailedRequests) { |
+ SimulateHasNetworkConnectivity(true); |
+ |
+ policy().SignalNetworkRequestNeeded(); |
+ ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(0, 0)); |
+ policy().InformOfNetworkRequestComplete(false); |
+ |
+ // Request after first failure should be delayed by 10 seconds % 50% fuzzing |
+ // factor. |
+ policy().SignalNetworkRequestNeeded(); |
+ ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
+ policy().InformOfNetworkRequestComplete(false); |
+ |
+ // Request after first failure should be delayed by 40 seconds % 50% fuzzing |
+ // factor, applied twice. |
+ policy().SignalNetworkRequestNeeded(); |
+ ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(10, 40)); |
+ policy().InformOfNetworkRequestComplete(false); |
+} |
+ |
+TEST_F(AffiliationFetchThrottlerTest, GracePeriodAfterConnectivityIsRestored) { |
+ SimulateHasNetworkConnectivity(false); |
+ |
+ // After connectivity is re-established, the first request should be delayed |
+ // by 10 seconds % 50% fuzzing factor. |
+ policy().SignalNetworkRequestNeeded(); |
+ SimulateHasNetworkConnectivity(true); |
+ ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
+ policy().InformOfNetworkRequestComplete(true); |
+ |
+ // The next request should not be delayed. |
+ policy().SignalNetworkRequestNeeded(); |
+ AssertReleaseBetweenSecs(0, 0); |
+} |
+ |
+// Same as GracePeriodAfterConnectivityIsRestored, but the network comes back |
+// just before SignalNetworkRequestNeeded() is called. |
+TEST_F(AffiliationFetchThrottlerTest, GracePeriodAfterConnectivityIsRestored2) { |
+ SimulateHasNetworkConnectivity(false); |
+ |
+ SimulateHasNetworkConnectivity(true); |
+ policy().SignalNetworkRequestNeeded(); |
+ ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
+ policy().InformOfNetworkRequestComplete(true); |
+ |
+ policy().SignalNetworkRequestNeeded(); |
+ AssertReleaseBetweenSecs(0, 0); |
+} |
+ |
+// TODO(engedy): This test is flaky. Investigate. |
+TEST_F(AffiliationFetchThrottlerTest, ConnectivityLostDuringBackoff) { |
+ SimulateHasNetworkConnectivity(true); |
+ |
+ policy().SignalNetworkRequestNeeded(); |
+ ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(0, 0)); |
+ policy().InformOfNetworkRequestComplete(false); |
+ |
+ policy().SignalNetworkRequestNeeded(); |
+ SimulateHasNetworkConnectivity(false); |
+ |
+ // Let the exponential backoff delay expire, and verify nothing happens. |
+ FastForwardUntilNoTasksRemain(); |
+ |
+ // Verify that the request is, however, sent after 10 seconds % 50 fuzzing |
+ // factor. |
+ SimulateHasNetworkConnectivity(true); |
+ ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
+} |
+ |
+TEST_F(AffiliationFetchThrottlerTest, |
+ ConnectivityLostAndRestoredDuringBackoff) { |
+ SimulateHasNetworkConnectivity(true); |
+ |
+ policy().SignalNetworkRequestNeeded(); |
+ ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(0, 0)); |
+ policy().InformOfNetworkRequestComplete(false); |
+ |
+ policy().SignalNetworkRequestNeeded(); |
+ |
+ // Also verify that a flaky connection will not flood the task queue with lots |
+ // of tasks. |
+ for (size_t t = 1; t <= 5; ++t) { |
+ SimulateHasNetworkConnectivity(false); |
+ SimulateHasNetworkConnectivity(true); |
+ FastForwardTimeBySecs(1); |
+ } |
+ EXPECT_EQ(1u, NumberOfEnqueuedTasks()); |
+ |
+ ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
+} |
+ |
+} // namespace password_manager |