OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include <queue> |
| 6 |
| 7 #include "base/callback.h" |
| 8 #include "base/memory/ref_counted.h" |
| 9 #include "base/run_loop.h" |
| 10 #include "base/single_thread_task_runner.h" |
| 11 #include "base/thread_task_runner_handle.h" |
| 12 #include "base/time/tick_clock.h" |
| 13 #include "base/time/time.h" |
| 14 #include "components/password_manager/core/browser/affiliation_fetch_throttler.h
" |
| 15 #include "testing/gmock/include/gmock/gmock.h" |
| 16 #include "testing/gtest/include/gtest/gtest.h" |
| 17 |
| 18 namespace password_manager { |
| 19 namespace { |
| 20 |
| 21 // A SingleThreadTaskRunner that mocks the current time and allows it to be |
| 22 // fast-forwarded. TODO(bartfab): Copies of this class exist in several tests. |
| 23 // Consolidate them (crbug.com/329911). |
| 24 class MockTimeSingleThreadTaskRunner : public base::SingleThreadTaskRunner { |
| 25 public: |
| 26 MockTimeSingleThreadTaskRunner(); |
| 27 |
| 28 // base::SingleThreadTaskRunner: |
| 29 virtual bool RunsTasksOnCurrentThread() const override; |
| 30 virtual bool PostDelayedTask(const tracked_objects::Location& from_here, |
| 31 const base::Closure& task, |
| 32 base::TimeDelta delay) override; |
| 33 virtual bool PostNonNestableDelayedTask( |
| 34 const tracked_objects::Location& from_here, |
| 35 const base::Closure& task, |
| 36 base::TimeDelta delay) override; |
| 37 |
| 38 const base::TimeTicks& GetCurrentTime() const; |
| 39 |
| 40 size_t task_queue_size() const { |
| 41 return tasks_.size(); |
| 42 } |
| 43 |
| 44 void FastForwardBy(base::TimeDelta delta); |
| 45 void FastForwardUntilNoTasksRemain(); |
| 46 |
| 47 private: |
| 48 // Strict weak temporal ordering of tasks. |
| 49 class TemporalOrder { |
| 50 public: |
| 51 bool operator()( |
| 52 const std::pair<base::TimeTicks, base::Closure>& first_task, |
| 53 const std::pair<base::TimeTicks, base::Closure>& second_task) const; |
| 54 }; |
| 55 |
| 56 virtual ~MockTimeSingleThreadTaskRunner(); |
| 57 |
| 58 base::TimeTicks now_; |
| 59 std::priority_queue<std::pair<base::TimeTicks, base::Closure>, |
| 60 std::vector<std::pair<base::TimeTicks, base::Closure>>, |
| 61 TemporalOrder> tasks_; |
| 62 |
| 63 DISALLOW_COPY_AND_ASSIGN(MockTimeSingleThreadTaskRunner); |
| 64 }; |
| 65 |
| 66 // A base::TickClock that uses a MockTimeSingleThreadTaskRunner as the source of |
| 67 // the current time. |
| 68 class MockClock : public base::TickClock { |
| 69 public: |
| 70 explicit MockClock(scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner); |
| 71 virtual ~MockClock(); |
| 72 |
| 73 // base::TickClock: |
| 74 virtual base::TimeTicks NowTicks() override; |
| 75 |
| 76 private: |
| 77 scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner_; |
| 78 |
| 79 DISALLOW_COPY_AND_ASSIGN(MockClock); |
| 80 }; |
| 81 |
| 82 MockTimeSingleThreadTaskRunner::MockTimeSingleThreadTaskRunner() { |
| 83 } |
| 84 |
| 85 bool MockTimeSingleThreadTaskRunner::RunsTasksOnCurrentThread() const { |
| 86 return true; |
| 87 } |
| 88 |
| 89 bool MockTimeSingleThreadTaskRunner::PostDelayedTask( |
| 90 const tracked_objects::Location& from_here, |
| 91 const base::Closure& task, |
| 92 base::TimeDelta delay) { |
| 93 tasks_.push(std::make_pair(now_ + delay, task)); |
| 94 return true; |
| 95 } |
| 96 |
| 97 bool MockTimeSingleThreadTaskRunner::PostNonNestableDelayedTask( |
| 98 const tracked_objects::Location& from_here, |
| 99 const base::Closure& task, |
| 100 base::TimeDelta delay) { |
| 101 NOTREACHED(); |
| 102 return false; |
| 103 } |
| 104 |
| 105 const base::TimeTicks& MockTimeSingleThreadTaskRunner::GetCurrentTime() const { |
| 106 return now_; |
| 107 } |
| 108 |
| 109 void MockTimeSingleThreadTaskRunner::FastForwardBy(base::TimeDelta delta) { |
| 110 const base::TimeTicks latest = now_ + delta; |
| 111 while (!tasks_.empty() && tasks_.top().first <= latest) { |
| 112 now_ = tasks_.top().first; |
| 113 base::Closure task = tasks_.top().second; |
| 114 tasks_.pop(); |
| 115 task.Run(); |
| 116 } |
| 117 now_ = latest; |
| 118 } |
| 119 |
| 120 void MockTimeSingleThreadTaskRunner::FastForwardUntilNoTasksRemain() { |
| 121 while (!tasks_.empty()) { |
| 122 now_ = tasks_.top().first; |
| 123 base::Closure task = tasks_.top().second; |
| 124 tasks_.pop(); |
| 125 task.Run(); |
| 126 } |
| 127 } |
| 128 |
| 129 bool MockTimeSingleThreadTaskRunner::TemporalOrder::operator()( |
| 130 const std::pair<base::TimeTicks, base::Closure>& first_task, |
| 131 const std::pair<base::TimeTicks, base::Closure>& second_task) const { |
| 132 return first_task.first > second_task.first; |
| 133 } |
| 134 |
| 135 MockTimeSingleThreadTaskRunner::~MockTimeSingleThreadTaskRunner() { |
| 136 } |
| 137 |
| 138 MockClock::MockClock(scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner) |
| 139 : task_runner_(task_runner) { |
| 140 } |
| 141 |
| 142 MockClock::~MockClock() { |
| 143 } |
| 144 |
| 145 base::TimeTicks MockClock::NowTicks() { |
| 146 return task_runner_->GetCurrentTime(); |
| 147 } |
| 148 |
| 149 class MockAffiliationFetchThrottlerDelegate |
| 150 : public testing::StrictMock<AffiliationFetchThrottlerDelegate> { |
| 151 public: |
| 152 MockAffiliationFetchThrottlerDelegate() {} |
| 153 ~MockAffiliationFetchThrottlerDelegate() {} |
| 154 |
| 155 MOCK_METHOD0(OnCanSendNetworkRequest, bool()); |
| 156 |
| 157 private: |
| 158 DISALLOW_COPY_AND_ASSIGN(MockAffiliationFetchThrottlerDelegate); |
| 159 }; |
| 160 |
| 161 class MockNetworkChangeNotifier : public net::NetworkChangeNotifier { |
| 162 public: |
| 163 MockNetworkChangeNotifier() : connection_type_(CONNECTION_UNKNOWN) {} |
| 164 |
| 165 void set_connection_type(ConnectionType connection_type) { |
| 166 connection_type_ = connection_type; |
| 167 NotifyObserversOfConnectionTypeChangeForTests(connection_type_); |
| 168 } |
| 169 |
| 170 ConnectionType GetCurrentConnectionType() const override { |
| 171 return connection_type_; |
| 172 } |
| 173 |
| 174 private: |
| 175 ConnectionType connection_type_; |
| 176 }; |
| 177 |
| 178 } // namespace |
| 179 |
| 180 class AffiliationFetchThrottlerTest : public testing::Test { |
| 181 public: |
| 182 AffiliationFetchThrottlerTest() |
| 183 : network_change_notifier_(new MockNetworkChangeNotifier), |
| 184 task_runner_(new MockTimeSingleThreadTaskRunner) {} |
| 185 ~AffiliationFetchThrottlerTest() override {} |
| 186 |
| 187 void SetUp() override { |
| 188 policy_.reset(new AffiliationFetchThrottler( |
| 189 &mock_delegate_, task_runner_, |
| 190 make_scoped_ptr(new MockClock(task_runner_)))); |
| 191 } |
| 192 |
| 193 void SimulateHasNetworkConnectivity(bool has_connectivity) { |
| 194 network_change_notifier_->set_connection_type(has_connectivity ? |
| 195 net::NetworkChangeNotifier::CONNECTION_UNKNOWN : |
| 196 net::NetworkChangeNotifier::CONNECTION_NONE); |
| 197 base::RunLoop().RunUntilIdle(); |
| 198 } |
| 199 |
| 200 // Forwards time, and given that MockAffiliationFetchThrottlerDelegate is a |
| 201 // strict mock, also ensures OnCanSendNetworkRequest() is not called back |
| 202 // during this period. |
| 203 void FastForwardTimeBySecs(double secs) { |
| 204 task_runner_->FastForwardBy(base::TimeDelta::FromSecondsD(secs)); |
| 205 } |
| 206 |
| 207 void FastForwardUntilNoTasksRemain() { |
| 208 task_runner_->FastForwardUntilNoTasksRemain(); |
| 209 } |
| 210 |
| 211 size_t NumberOfEnqueuedTasks() { |
| 212 return task_runner_->task_queue_size(); |
| 213 } |
| 214 |
| 215 // Forwards time until the next OnCanSendNetworkRequest() callback, and |
| 216 // verifies that it occurs between |min_delay_sec| and |max_delay_sec| seconds |
| 217 // from now, inclusive. |
| 218 void AssertReleaseBetweenSecs(double min_delay_sec, double max_delay_sec) { |
| 219 base::TimeTicks ticks_at_start = task_runner_->GetCurrentTime(); |
| 220 base::TimeDelta min_delay = base::TimeDelta::FromSecondsD(min_delay_sec); |
| 221 if (min_delay.ToInternalValue() > 0) { |
| 222 task_runner_->FastForwardBy(min_delay - |
| 223 base::TimeDelta::FromInternalValue(1)); |
| 224 } |
| 225 EXPECT_CALL(mock_delegate_, OnCanSendNetworkRequest()) |
| 226 .Times(1) |
| 227 .WillRepeatedly(testing::Return(true)); |
| 228 task_runner_->FastForwardUntilNoTasksRemain(); |
| 229 ASSERT_TRUE(testing::Mock::VerifyAndClearExpectations(&mock_delegate_)); |
| 230 ASSERT_GE(base::TimeDelta::FromSecondsD(max_delay_sec), |
| 231 task_runner_->GetCurrentTime() - ticks_at_start); |
| 232 } |
| 233 |
| 234 AffiliationFetchThrottler& policy() { return *policy_.get(); } |
| 235 |
| 236 private: |
| 237 // Needed because NetworkChangeNotifier uses ObserverList, which notifies |
| 238 // observers on the MessageLoop that belongs to the thread from which they |
| 239 // have registered. |
| 240 base::MessageLoop message_loop_; |
| 241 scoped_ptr<MockNetworkChangeNotifier> network_change_notifier_; |
| 242 scoped_refptr<MockTimeSingleThreadTaskRunner> task_runner_; |
| 243 MockAffiliationFetchThrottlerDelegate mock_delegate_; |
| 244 scoped_ptr<AffiliationFetchThrottler> policy_; |
| 245 |
| 246 DISALLOW_COPY_AND_ASSIGN(AffiliationFetchThrottlerTest); |
| 247 }; |
| 248 |
| 249 TEST_F(AffiliationFetchThrottlerTest, SuccessfulRequests) { |
| 250 SimulateHasNetworkConnectivity(true); |
| 251 |
| 252 policy().SignalNetworkRequestNeeded(); |
| 253 AssertReleaseBetweenSecs(0, 0); |
| 254 |
| 255 // Signal while request is in flight should be ignored. |
| 256 policy().SignalNetworkRequestNeeded(); |
| 257 FastForwardUntilNoTasksRemain(); |
| 258 policy().InformOfNetworkRequestComplete(true); |
| 259 FastForwardUntilNoTasksRemain(); |
| 260 |
| 261 // Duplicate the second signal 3 times: still only 1 callback should arrive. |
| 262 policy().SignalNetworkRequestNeeded(); |
| 263 policy().SignalNetworkRequestNeeded(); |
| 264 policy().SignalNetworkRequestNeeded(); |
| 265 AssertReleaseBetweenSecs(0, 0); |
| 266 } |
| 267 |
| 268 TEST_F(AffiliationFetchThrottlerTest, FailedRequests) { |
| 269 SimulateHasNetworkConnectivity(true); |
| 270 |
| 271 policy().SignalNetworkRequestNeeded(); |
| 272 ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(0, 0)); |
| 273 policy().InformOfNetworkRequestComplete(false); |
| 274 |
| 275 // Request after first failure should be delayed by 10 seconds % 50% fuzzing |
| 276 // factor. |
| 277 policy().SignalNetworkRequestNeeded(); |
| 278 ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
| 279 policy().InformOfNetworkRequestComplete(false); |
| 280 |
| 281 // Request after first failure should be delayed by 40 seconds % 50% fuzzing |
| 282 // factor, applied twice. |
| 283 policy().SignalNetworkRequestNeeded(); |
| 284 ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(10, 40)); |
| 285 policy().InformOfNetworkRequestComplete(false); |
| 286 } |
| 287 |
| 288 TEST_F(AffiliationFetchThrottlerTest, GracePeriodAfterConnectivityIsRestored) { |
| 289 SimulateHasNetworkConnectivity(false); |
| 290 |
| 291 // After connectivity is re-established, the first request should be delayed |
| 292 // by 10 seconds % 50% fuzzing factor. |
| 293 policy().SignalNetworkRequestNeeded(); |
| 294 SimulateHasNetworkConnectivity(true); |
| 295 ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
| 296 policy().InformOfNetworkRequestComplete(true); |
| 297 |
| 298 // The next request should not be delayed. |
| 299 policy().SignalNetworkRequestNeeded(); |
| 300 AssertReleaseBetweenSecs(0, 0); |
| 301 } |
| 302 |
| 303 // Same as GracePeriodAfterConnectivityIsRestored, but the network comes back |
| 304 // just before SignalNetworkRequestNeeded() is called. |
| 305 TEST_F(AffiliationFetchThrottlerTest, |
| 306 GracePeriodAfterConnectivityIsRestored2) { |
| 307 SimulateHasNetworkConnectivity(false); |
| 308 |
| 309 SimulateHasNetworkConnectivity(true); |
| 310 policy().SignalNetworkRequestNeeded(); |
| 311 ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
| 312 policy().InformOfNetworkRequestComplete(true); |
| 313 |
| 314 policy().SignalNetworkRequestNeeded(); |
| 315 AssertReleaseBetweenSecs(0, 0); |
| 316 } |
| 317 |
| 318 // TODO(engedy): This test is flaky. Investigate. |
| 319 TEST_F(AffiliationFetchThrottlerTest, ConnectivityLostDuringBackoff) { |
| 320 SimulateHasNetworkConnectivity(true); |
| 321 |
| 322 policy().SignalNetworkRequestNeeded(); |
| 323 ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(0, 0)); |
| 324 policy().InformOfNetworkRequestComplete(false); |
| 325 |
| 326 policy().SignalNetworkRequestNeeded(); |
| 327 SimulateHasNetworkConnectivity(false); |
| 328 |
| 329 // Let the exponential backoff delay expire, and verify nothing happens. |
| 330 FastForwardUntilNoTasksRemain(); |
| 331 |
| 332 // Verify that the request is, however, sent after 10 seconds % 50 fuzzing |
| 333 // factor. |
| 334 SimulateHasNetworkConnectivity(true); |
| 335 ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
| 336 } |
| 337 |
| 338 TEST_F(AffiliationFetchThrottlerTest, |
| 339 ConnectivityLostAndRestoredDuringBackoff) { |
| 340 SimulateHasNetworkConnectivity(true); |
| 341 |
| 342 policy().SignalNetworkRequestNeeded(); |
| 343 ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(0, 0)); |
| 344 policy().InformOfNetworkRequestComplete(false); |
| 345 |
| 346 policy().SignalNetworkRequestNeeded(); |
| 347 |
| 348 // Also verify that a flaky connection will not flood the task queue with lots |
| 349 // of tasks. |
| 350 for (size_t t = 1; t <= 5; ++t) { |
| 351 SimulateHasNetworkConnectivity(false); |
| 352 SimulateHasNetworkConnectivity(true); |
| 353 FastForwardTimeBySecs(1); |
| 354 } |
| 355 EXPECT_EQ(1u, NumberOfEnqueuedTasks()); |
| 356 |
| 357 ASSERT_NO_FATAL_FAILURE(AssertReleaseBetweenSecs(5, 10)); |
| 358 } |
| 359 |
| 360 |
| 361 } // namespace password_manager |
OLD | NEW |