Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(69)

Unified Diff: components/password_manager/core/browser/affiliation_fetch_throttler_unittest.cc

Issue 807503002: Implement throttling logic for fetching affiliation information. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@aff_database
Patch Set: Fix header guards. Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698