| 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..3686696729963c17576f68e2913f0185a72616e1
|
| --- /dev/null
|
| +++ b/components/password_manager/core/browser/affiliation_fetch_throttler_unittest.cc
|
| @@ -0,0 +1,417 @@
|
| +// 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 "components/password_manager/core/browser/affiliation_fetch_throttler.h"
|
| +
|
| +#include <cmath>
|
| +
|
| +#include "base/callback.h"
|
| +#include "base/macros.h"
|
| +#include "base/memory/ref_counted.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/message_loop/message_loop.h"
|
| +#include "base/numerics/safe_math.h"
|
| +#include "base/run_loop.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_delegate.h"
|
| +#include "net/base/network_change_notifier.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +namespace password_manager {
|
| +namespace {
|
| +
|
| +class MockAffiliationFetchThrottlerDelegate
|
| + : public AffiliationFetchThrottlerDelegate {
|
| + public:
|
| + explicit MockAffiliationFetchThrottlerDelegate(
|
| + scoped_ptr<base::TickClock> tick_clock)
|
| + : tick_clock_(tick_clock.Pass()),
|
| + emulated_return_value_(true),
|
| + can_send_count_(0u) {}
|
| + ~MockAffiliationFetchThrottlerDelegate() override {
|
| + EXPECT_EQ(0u, can_send_count_);
|
| + }
|
| +
|
| + void set_emulated_return_value(bool value) { emulated_return_value_ = value; }
|
| + void reset_can_send_count() { can_send_count_ = 0u; }
|
| + size_t can_send_count() const { return can_send_count_; }
|
| + base::TimeTicks last_can_send_time() const { return last_can_send_time_; }
|
| +
|
| + // AffiliationFetchThrottlerDelegate:
|
| + bool OnCanSendNetworkRequest() override {
|
| + ++can_send_count_;
|
| + last_can_send_time_ = tick_clock_->NowTicks();
|
| + return emulated_return_value_;
|
| + }
|
| +
|
| + private:
|
| + scoped_ptr<base::TickClock> tick_clock_;
|
| + bool emulated_return_value_;
|
| + size_t can_send_count_;
|
| + base::TimeTicks last_can_send_time_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(MockAffiliationFetchThrottlerDelegate);
|
| +};
|
| +
|
| +} // namespace
|
| +
|
| +class AffiliationFetchThrottlerTest : public testing::Test {
|
| + public:
|
| + AffiliationFetchThrottlerTest()
|
| + : network_change_notifier_(net::NetworkChangeNotifier::CreateMock()),
|
| + task_runner_(new base::TestMockTimeTaskRunner),
|
| + mock_delegate_(task_runner_->GetMockTickClock()) {}
|
| + ~AffiliationFetchThrottlerTest() override {}
|
| +
|
| + scoped_ptr<AffiliationFetchThrottler> CreateThrottler() {
|
| + return make_scoped_ptr(new AffiliationFetchThrottler(
|
| + &mock_delegate_, task_runner_, task_runner_->GetMockTickClock()));
|
| + }
|
| +
|
| + void SimulateHasNetworkConnectivity(bool has_connectivity) {
|
| + net::NetworkChangeNotifier::NotifyObserversOfConnectionTypeChangeForTests(
|
| + has_connectivity ? net::NetworkChangeNotifier::CONNECTION_UNKNOWN
|
| + : net::NetworkChangeNotifier::CONNECTION_NONE);
|
| + base::RunLoop().RunUntilIdle();
|
| + }
|
| +
|
| + // Runs the task runner until no tasks remain, and asserts that by this time,
|
| + // OnCanSendNetworkRequest() will have been called exactly once, with a delay
|
| + // between |min_delay_ms| and |max_delay_ms|, modulo 0.5 ms to allow for
|
| + // floating point errors. When OnCanSendNetworkRequest() is called, the mock
|
| + // will return |emulated_return_value|. This value normally indicates whether
|
| + // or not a request was actually issued in response to the call.
|
| + void AssertReleaseInBetween(bool emulated_return_value,
|
| + double min_delay_ms,
|
| + double max_delay_ms) {
|
| + ASSERT_EQ(0u, mock_delegate_.can_send_count());
|
| + base::TimeTicks ticks_at_start = task_runner_->GetCurrentMockTime();
|
| + mock_delegate_.set_emulated_return_value(emulated_return_value);
|
| + task_runner_->FastForwardUntilNoTasksRemain();
|
| + ASSERT_EQ(1u, mock_delegate_.can_send_count());
|
| + base::TimeDelta delay =
|
| + mock_delegate_.last_can_send_time() - ticks_at_start;
|
| + EXPECT_LE(min_delay_ms - 1, delay.InMillisecondsF());
|
| + EXPECT_GE(max_delay_ms + 1, delay.InMillisecondsF());
|
| + mock_delegate_.reset_can_send_count();
|
| + }
|
| +
|
| + // Runs the task runner for |secs| and asserts that OnCanSendNetworkRequest()
|
| + // will not have been called by the end of this period.
|
| + void AssertNoReleaseForSecs(int64_t secs) {
|
| + task_runner_->FastForwardBy(base::TimeDelta::FromSeconds(secs));
|
| + ASSERT_EQ(0u, mock_delegate_.can_send_count());
|
| + }
|
| +
|
| + // Runs the task runner until no tasks remain, and asserts that
|
| + // OnCanSendNetworkRequest() will not have been called.
|
| + void AssertNoReleaseUntilNoTasksRemain() {
|
| + task_runner_->FastForwardUntilNoTasksRemain();
|
| + ASSERT_EQ(0u, mock_delegate_.can_send_count());
|
| + }
|
| +
|
| + size_t GetPendingTaskCount() const {
|
| + return task_runner_->GetPendingTaskCount();
|
| + }
|
| +
|
| + 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<net::NetworkChangeNotifier> network_change_notifier_;
|
| + scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
|
| + MockAffiliationFetchThrottlerDelegate mock_delegate_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(AffiliationFetchThrottlerTest);
|
| +};
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest, SuccessfulRequests) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| +
|
| + // Signal while request is in flight should be ignored.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| + throttler->InformOfNetworkRequestComplete(true);
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| +
|
| + // Duplicate the second signal 3 times: still only 1 callback should arrive.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + throttler->SignalNetworkRequestNeeded();
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest, FailedRequests) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| +
|
| + // The request after the first failure should be delayed by |initial_delay_ms|
|
| + // spread out over Uniform(1 - |jitter_factor|, 1).
|
| + throttler->SignalNetworkRequestNeeded();
|
| + const auto& kPolicy = AffiliationFetchThrottler::kBackoffPolicy;
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(
|
| + true, kPolicy.initial_delay_ms * (1 - kPolicy.jitter_factor),
|
| + kPolicy.initial_delay_ms));
|
| + throttler->InformOfNetworkRequestComplete(true);
|
| +
|
| + // After a successful request, the next one should be released immediately.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| +
|
| + // In general, the request after the n-th failure should be delayed by
|
| + // |multiply_factor| ^ (n-1) * |initial_delay_ms|,
|
| + // spread out over Uniform(1 - |jitter_factor|, 1), up until
|
| + // |maximum_backoff_ms|
|
| + // is reached.
|
| + for (int num_failures = 1; num_failures < 100; ++num_failures) {
|
| + throttler->SignalNetworkRequestNeeded();
|
| + double max_delay_ms = kPolicy.initial_delay_ms *
|
| + pow(kPolicy.multiply_factor, num_failures - 1);
|
| + double min_delay_ms = max_delay_ms * (1 - kPolicy.jitter_factor);
|
| + if (max_delay_ms > kPolicy.maximum_backoff_ms)
|
| + max_delay_ms = kPolicy.maximum_backoff_ms;
|
| + if (min_delay_ms > kPolicy.maximum_backoff_ms)
|
| + min_delay_ms = kPolicy.maximum_backoff_ms;
|
| + ASSERT_NO_FATAL_FAILURE(
|
| + AssertReleaseInBetween(true, min_delay_ms, max_delay_ms));
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| + }
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest, OnCanSendNetworkRequestReturnsFalse) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + // A need for a network request is signaled, but as OnCanSendNetworkRequest()
|
| + // is called, the implementation returns false to indicate that the request
|
| + // will not be needed after all. InformOfNetworkRequestComplete() must not be
|
| + // called in this case.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(false, 0, 0));
|
| +
|
| + // A subsequent signaling, however, should result in OnCanSendNetworkRequest()
|
| + // being called immediately.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest, GracePeriodAfterConnectivityIsRestored) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| + SimulateHasNetworkConnectivity(false);
|
| +
|
| + // After connectivity is restored, the first request should be delayed by the
|
| + // grace period, spread out over Uniform(1 - |jitter_factor|, 1).
|
| + throttler->SignalNetworkRequestNeeded();
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| +
|
| + SimulateHasNetworkConnectivity(true);
|
| + const auto& kPolicy = AffiliationFetchThrottler::kBackoffPolicy;
|
| + const int64_t& kGraceMs =
|
| + AffiliationFetchThrottler::kGracePeriodAfterReconnectMs;
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(
|
| + true, kGraceMs * (1 - kPolicy.jitter_factor), kGraceMs));
|
| + throttler->InformOfNetworkRequestComplete(true);
|
| +
|
| + // The next request should not be delayed.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| +}
|
| +
|
| +// Same as GracePeriodAfterConnectivityIsRestored, but the network comes back
|
| +// just before SignalNetworkRequestNeeded() is called.
|
| +TEST_F(AffiliationFetchThrottlerTest, GracePeriodAfterConnectivityIsRestored2) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| + SimulateHasNetworkConnectivity(false);
|
| +
|
| + SimulateHasNetworkConnectivity(true);
|
| + throttler->SignalNetworkRequestNeeded();
|
| + const auto& kPolicy = AffiliationFetchThrottler::kBackoffPolicy;
|
| + const int64_t& kGraceMs =
|
| + AffiliationFetchThrottler::kGracePeriodAfterReconnectMs;
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(
|
| + true, kGraceMs * (1 - kPolicy.jitter_factor), kGraceMs));
|
| + throttler->InformOfNetworkRequestComplete(true);
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest, ConnectivityLostDuringBackoff) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + SimulateHasNetworkConnectivity(false);
|
| +
|
| + // Let the exponential backoff delay expire, and verify nothing happens.
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| +
|
| + // Verify that the request is, however, sent after the normal grace period
|
| + // once connectivity is restored.
|
| + SimulateHasNetworkConnectivity(true);
|
| + const auto& kPolicy = AffiliationFetchThrottler::kBackoffPolicy;
|
| + const int64_t& kGraceMs =
|
| + AffiliationFetchThrottler::kGracePeriodAfterReconnectMs;
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(
|
| + true, kGraceMs * (1 - kPolicy.jitter_factor), kGraceMs));
|
| + throttler->InformOfNetworkRequestComplete(true);
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest,
|
| + ConnectivityLostAndRestoredDuringBackoff) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + const auto& kPolicy = AffiliationFetchThrottler::kBackoffPolicy;
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(
|
| + true, kPolicy.initial_delay_ms * (1 - kPolicy.jitter_factor),
|
| + kPolicy.initial_delay_ms));
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| +
|
| + SimulateHasNetworkConnectivity(false);
|
| + SimulateHasNetworkConnectivity(true);
|
| +
|
| + // This test expects that the exponential backoff interval after the 2nd error
|
| + // is larger than the normal grace period after connectivity is restored.
|
| + const int64_t& kGraceMs =
|
| + AffiliationFetchThrottler::kGracePeriodAfterReconnectMs;
|
| + EXPECT_PRED_FORMAT2(testing::DoubleLE, kGraceMs,
|
| + kPolicy.initial_delay_ms * kPolicy.multiply_factor);
|
| +
|
| + // The release should come after the longest of the two intervals expire.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(
|
| + true, kPolicy.initial_delay_ms * kPolicy.multiply_factor *
|
| + (1 - kPolicy.jitter_factor),
|
| + kPolicy.initial_delay_ms * kPolicy.multiply_factor));
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest, FlakyConnectivity) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| +
|
| + // Run for a total of 5 grace periods and simulate connectivity being lost and
|
| + // restored every second. This verifies that a flaky connection will not flood
|
| + // the task queue with lots of of tasks and also that release will not happen
|
| + // while the connection is flaky even once the first grace period has expired.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + const auto& kPolicy = AffiliationFetchThrottler::kBackoffPolicy;
|
| + const int64_t& kGraceMs =
|
| + AffiliationFetchThrottler::kGracePeriodAfterReconnectMs;
|
| + int64_t five_grace_periods_secs =
|
| + kGraceMs * 5 / base::Time::kMillisecondsPerSecond;
|
| + for (int64_t t = 0; t < five_grace_periods_secs; ++t) {
|
| + SimulateHasNetworkConnectivity(false);
|
| + AssertNoReleaseForSecs(1);
|
| + SimulateHasNetworkConnectivity(true);
|
| + EXPECT_EQ(1u, GetPendingTaskCount());
|
| + }
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(
|
| + true, kGraceMs * (1 - kPolicy.jitter_factor), kGraceMs));
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest, ConnectivityLostDuringRequest) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| +
|
| + SimulateHasNetworkConnectivity(false);
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| + throttler->SignalNetworkRequestNeeded();
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| +
|
| + SimulateHasNetworkConnectivity(true);
|
| +
|
| + // Verify that the next request is released after the normal grace period.
|
| + const auto& kPolicy = AffiliationFetchThrottler::kBackoffPolicy;
|
| + const int64_t& kGraceMs =
|
| + AffiliationFetchThrottler::kGracePeriodAfterReconnectMs;
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(
|
| + true, kGraceMs * (1 - kPolicy.jitter_factor), kGraceMs));
|
| + throttler->InformOfNetworkRequestComplete(true);
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest,
|
| + ConnectivityLostAndRestoredDuringRequest) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| +
|
| + SimulateHasNetworkConnectivity(false);
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| + SimulateHasNetworkConnectivity(true);
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| + throttler->InformOfNetworkRequestComplete(true);
|
| +
|
| + // Even though the previous request succeeded, the next request should still
|
| + // be held back for the normal grace period after connection is restored.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + const auto& kPolicy = AffiliationFetchThrottler::kBackoffPolicy;
|
| + const int64_t& kGraceMs =
|
| + AffiliationFetchThrottler::kGracePeriodAfterReconnectMs;
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(
|
| + true, kGraceMs * (1 - kPolicy.jitter_factor), kGraceMs));
|
| + throttler->InformOfNetworkRequestComplete(true);
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest,
|
| + ConnectivityLostAndRestoredDuringRequest2) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| +
|
| + SimulateHasNetworkConnectivity(false);
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| + SimulateHasNetworkConnectivity(true);
|
| +
|
| + const int64_t& kGraceMs =
|
| + AffiliationFetchThrottler::kGracePeriodAfterReconnectMs;
|
| + AssertNoReleaseForSecs(kGraceMs / base::Time::kMillisecondsPerSecond);
|
| + throttler->InformOfNetworkRequestComplete(true);
|
| +
|
| + // The next request should not be held back.
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| +}
|
| +
|
| +TEST_F(AffiliationFetchThrottlerTest, InstanceDestroyedWhileInBackoff) {
|
| + scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler());
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0));
|
| + throttler->InformOfNetworkRequestComplete(false);
|
| +
|
| + throttler->SignalNetworkRequestNeeded();
|
| + throttler.reset();
|
| + EXPECT_EQ(1u, GetPendingTaskCount());
|
| + AssertNoReleaseUntilNoTasksRemain();
|
| +}
|
| +
|
| +} // namespace password_manager
|
|
|