| 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..70161311c587ee4a0189c193f53bcd1881237c4c
|
| --- /dev/null
|
| +++ b/components/password_manager/core/browser/affiliation_fetch_throttler_unittest.cc
|
| @@ -0,0 +1,361 @@
|
| +// Copyright 2014 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/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 {
|
| +
|
| +// A SingleThreadTaskRunner that mocks the current time and allows it to be
|
| +// fast-forwarded. TODO(bartfab): Copies of this class exist in several tests.
|
| +// Consolidate them (crbug.com/329911).
|
| +class MockTimeSingleThreadTaskRunner : public base::SingleThreadTaskRunner {
|
| + public:
|
| + MockTimeSingleThreadTaskRunner();
|
| +
|
| + // base::SingleThreadTaskRunner:
|
| + virtual bool RunsTasksOnCurrentThread() const override;
|
| + virtual bool PostDelayedTask(const tracked_objects::Location& from_here,
|
| + const base::Closure& task,
|
| + base::TimeDelta delay) override;
|
| + virtual bool PostNonNestableDelayedTask(
|
| + const tracked_objects::Location& from_here,
|
| + const base::Closure& task,
|
| + base::TimeDelta delay) override;
|
| +
|
| + const base::TimeTicks& GetCurrentTime() const;
|
| +
|
| + size_t task_queue_size() const {
|
| + return tasks_.size();
|
| + }
|
| +
|
| + void FastForwardBy(base::TimeDelta delta);
|
| + void FastForwardUntilNoTasksRemain();
|
| +
|
| + private:
|
| + // Strict weak temporal ordering of tasks.
|
| + class TemporalOrder {
|
| + public:
|
| + bool operator()(
|
| + const std::pair<base::TimeTicks, base::Closure>& first_task,
|
| + const std::pair<base::TimeTicks, base::Closure>& second_task) const;
|
| + };
|
| +
|
| + virtual ~MockTimeSingleThreadTaskRunner();
|
| +
|
| + base::TimeTicks now_;
|
| + std::priority_queue<std::pair<base::TimeTicks, base::Closure>,
|
| + std::vector<std::pair<base::TimeTicks, base::Closure>>,
|
| + TemporalOrder> tasks_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(MockTimeSingleThreadTaskRunner);
|
| +};
|
| +
|
| +// A base::TickClock that uses a MockTimeSingleThreadTaskRunner as the source of
|
| +// the current time.
|
| +class MockClock : public base::TickClock {
|
| + public:
|
| + explicit MockClock(scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner);
|
| + virtual ~MockClock();
|
| +
|
| + // base::TickClock:
|
| + virtual base::TimeTicks NowTicks() override;
|
| +
|
| + private:
|
| + scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(MockClock);
|
| +};
|
| +
|
| +MockTimeSingleThreadTaskRunner::MockTimeSingleThreadTaskRunner() {
|
| +}
|
| +
|
| +bool MockTimeSingleThreadTaskRunner::RunsTasksOnCurrentThread() const {
|
| + return true;
|
| +}
|
| +
|
| +bool MockTimeSingleThreadTaskRunner::PostDelayedTask(
|
| + const tracked_objects::Location& from_here,
|
| + const base::Closure& task,
|
| + base::TimeDelta delay) {
|
| + tasks_.push(std::make_pair(now_ + delay, task));
|
| + return true;
|
| +}
|
| +
|
| +bool MockTimeSingleThreadTaskRunner::PostNonNestableDelayedTask(
|
| + const tracked_objects::Location& from_here,
|
| + const base::Closure& task,
|
| + base::TimeDelta delay) {
|
| + NOTREACHED();
|
| + return false;
|
| +}
|
| +
|
| +const base::TimeTicks& MockTimeSingleThreadTaskRunner::GetCurrentTime() const {
|
| + return now_;
|
| +}
|
| +
|
| +void MockTimeSingleThreadTaskRunner::FastForwardBy(base::TimeDelta delta) {
|
| + const base::TimeTicks latest = now_ + delta;
|
| + while (!tasks_.empty() && tasks_.top().first <= latest) {
|
| + now_ = tasks_.top().first;
|
| + base::Closure task = tasks_.top().second;
|
| + tasks_.pop();
|
| + task.Run();
|
| + }
|
| + now_ = latest;
|
| +}
|
| +
|
| +void MockTimeSingleThreadTaskRunner::FastForwardUntilNoTasksRemain() {
|
| + while (!tasks_.empty()) {
|
| + now_ = tasks_.top().first;
|
| + base::Closure task = tasks_.top().second;
|
| + tasks_.pop();
|
| + task.Run();
|
| + }
|
| +}
|
| +
|
| +bool MockTimeSingleThreadTaskRunner::TemporalOrder::operator()(
|
| + const std::pair<base::TimeTicks, base::Closure>& first_task,
|
| + const std::pair<base::TimeTicks, base::Closure>& second_task) const {
|
| + return first_task.first > second_task.first;
|
| +}
|
| +
|
| +MockTimeSingleThreadTaskRunner::~MockTimeSingleThreadTaskRunner() {
|
| +}
|
| +
|
| +MockClock::MockClock(scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner)
|
| + : task_runner_(task_runner) {
|
| +}
|
| +
|
| +MockClock::~MockClock() {
|
| +}
|
| +
|
| +base::TimeTicks MockClock::NowTicks() {
|
| + return task_runner_->GetCurrentTime();
|
| +}
|
| +
|
| +class MockAffiliationFetchThrottlerDelegate
|
| + : public testing::StrictMock<AffiliationFetchThrottlerDelegate> {
|
| + public:
|
| + MockAffiliationFetchThrottlerDelegate() {}
|
| + ~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 MockTimeSingleThreadTaskRunner) {}
|
| + ~AffiliationFetchThrottlerTest() override {}
|
| +
|
| + void SetUp() override {
|
| + policy_.reset(new AffiliationFetchThrottler(
|
| + &mock_delegate_, task_runner_,
|
| + make_scoped_ptr(new MockClock(task_runner_))));
|
| + }
|
| +
|
| + 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_->task_queue_size();
|
| + }
|
| +
|
| + // 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<MockTimeSingleThreadTaskRunner> 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
|
|
|