OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 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 "components/password_manager/core/browser/affiliation_fetch_throttler.h " | |
6 | |
7 #include <cmath> | |
8 | |
9 #include "base/callback.h" | |
10 #include "base/macros.h" | |
11 #include "base/memory/ref_counted.h" | |
12 #include "base/memory/scoped_ptr.h" | |
13 #include "base/message_loop/message_loop.h" | |
14 #include "base/numerics/safe_math.h" | |
15 #include "base/run_loop.h" | |
16 #include "base/test/test_mock_time_task_runner.h" | |
17 #include "base/thread_task_runner_handle.h" | |
18 #include "base/time/tick_clock.h" | |
19 #include "base/time/time.h" | |
20 #include "components/password_manager/core/browser/affiliation_fetch_throttler_d elegate.h" | |
21 #include "net/base/network_change_notifier.h" | |
22 #include "testing/gtest/include/gtest/gtest.h" | |
23 | |
24 namespace password_manager { | |
25 namespace { | |
26 | |
27 class MockAffiliationFetchThrottlerDelegate | |
28 : public AffiliationFetchThrottlerDelegate { | |
29 public: | |
30 MockAffiliationFetchThrottlerDelegate(scoped_ptr<base::TickClock> tick_clock) | |
31 : tick_clock_(tick_clock.Pass()), | |
32 emulated_return_value_(true), | |
33 release_count_(0u) {} | |
34 ~MockAffiliationFetchThrottlerDelegate() { EXPECT_EQ(0u, release_count_); } | |
35 | |
36 void set_emulated_return_value(bool value) { emulated_return_value_ = value; } | |
37 void reset_release_count() { release_count_ = 0u; } | |
38 size_t release_count() const { return release_count_; } | |
39 base::TimeTicks last_release_time() const { return last_release_time_; } | |
40 | |
41 // AffiliationFetchThrottlerDelegate: | |
42 bool OnCanSendNetworkRequest() override { | |
43 ++release_count_; | |
44 last_release_time_ = tick_clock_->NowTicks(); | |
45 return emulated_return_value_; | |
46 } | |
47 | |
48 private: | |
49 scoped_ptr<base::TickClock> tick_clock_; | |
50 bool emulated_return_value_; | |
51 size_t release_count_; | |
52 base::TimeTicks last_release_time_; | |
53 | |
54 DISALLOW_COPY_AND_ASSIGN(MockAffiliationFetchThrottlerDelegate); | |
55 }; | |
56 | |
57 class MockNetworkChangeNotifier : public net::NetworkChangeNotifier { | |
mmenke
2015/01/26 18:49:13
This class isn't needed. See NetworkChangeNotifie
engedy
2015/01/26 19:39:35
Done.
| |
58 public: | |
59 MockNetworkChangeNotifier() : connection_type_(CONNECTION_UNKNOWN) {} | |
60 | |
61 void set_connection_type(ConnectionType connection_type) { | |
62 connection_type_ = connection_type; | |
63 NotifyObserversOfConnectionTypeChangeForTests(connection_type_); | |
64 } | |
65 | |
66 ConnectionType GetCurrentConnectionType() const override { | |
67 return connection_type_; | |
68 } | |
69 | |
70 private: | |
71 ConnectionType connection_type_; | |
72 }; | |
73 | |
74 // Rounds the provided time delta in milliseconds (potentially having fractional | |
75 // part) to the nearest microsecond and returns it. | |
76 base::TimeDelta RoundToNearestMicrosecond(double delta_ms) { | |
77 base::internal::CheckedNumeric<int64_t> delta_us( | |
78 delta_ms * base::Time::kMicrosecondsPerMillisecond + .5); | |
79 return base::TimeDelta::FromMicroseconds(delta_us.ValueOrDie()); | |
80 } | |
81 | |
82 } // namespace | |
83 | |
84 class AffiliationFetchThrottlerTest : public testing::Test { | |
85 public: | |
86 AffiliationFetchThrottlerTest() | |
87 : task_runner_(new base::TestMockTimeTaskRunner), | |
88 mock_delegate_(task_runner_->GetMockTickClock()) {} | |
89 ~AffiliationFetchThrottlerTest() override {} | |
90 | |
91 scoped_ptr<AffiliationFetchThrottler> CreateThrottler() { | |
92 return make_scoped_ptr(new AffiliationFetchThrottler( | |
93 &mock_delegate_, task_runner_, task_runner_->GetMockTickClock())); | |
94 } | |
95 | |
96 void SimulateHasNetworkConnectivity(bool has_connectivity) { | |
97 network_change_notifier_.set_connection_type( | |
98 has_connectivity ? net::NetworkChangeNotifier::CONNECTION_UNKNOWN | |
99 : net::NetworkChangeNotifier::CONNECTION_NONE); | |
100 base::RunLoop().RunUntilIdle(); | |
101 } | |
102 | |
103 // Asserts that OnCanSendNetworkRequest() will have been called once by the | |
104 // time no tasks remain in the task runner, with an expected delay of at least | |
105 // |min_delay_ms| but no more than |max_delay_ms|. When called, the mock will | |
106 // return |emulated_return_value|, a boolean value normally indicating whether | |
107 // or not a request was actually issued in response to the call. | |
108 void AssertReleaseInBetween(bool emulated_return_value, | |
109 double min_delay_ms, | |
110 double max_delay_ms) { | |
111 ASSERT_EQ(0u, mock_delegate_.release_count()); | |
112 base::TimeTicks ticks_at_start = task_runner_->GetCurrentMockTime(); | |
113 mock_delegate_.set_emulated_return_value(emulated_return_value); | |
114 task_runner_->FastForwardUntilNoTasksRemain(); | |
115 base::TimeDelta delay = mock_delegate_.last_release_time() - ticks_at_start; | |
116 ASSERT_EQ(1u, mock_delegate_.release_count()); | |
117 EXPECT_LE(RoundToNearestMicrosecond(min_delay_ms), delay); | |
118 EXPECT_GE(RoundToNearestMicrosecond(max_delay_ms), delay); | |
mmenke
2015/01/26 18:49:13
Per comment, it may theoretically be possible for
engedy
2015/01/26 19:39:34
The reasons that it should never occur is because
mmenke
2015/01/30 19:50:41
Hrm...I thought I checked and it was truncated...M
| |
119 mock_delegate_.reset_release_count(); | |
120 } | |
121 | |
122 // Runs the task runner for |ms| and asserts that OnCanSendNetworkRequest() | |
123 // will not have been called by the end of this period. | |
124 void AssertNoReleaseForMs(double ms) { | |
125 task_runner_->FastForwardBy(RoundToNearestMicrosecond(ms)); | |
126 ASSERT_EQ(0u, mock_delegate_.release_count()); | |
127 } | |
128 | |
129 // Runs the task runner until no tasks remain, and asserts that | |
130 // OnCanSendNetworkRequest() will not have been called. | |
131 void AssertNoReleaseUntilNoTasksRemain() { | |
132 task_runner_->FastForwardUntilNoTasksRemain(); | |
133 ASSERT_EQ(0u, mock_delegate_.release_count()); | |
134 } | |
135 | |
136 size_t GetPendingTaskCount() const { | |
137 return task_runner_->GetPendingTaskCount(); | |
138 } | |
139 | |
140 private: | |
141 // Needed because NetworkChangeNotifier uses ObserverList, which notifies | |
142 // observers on the MessageLoop that belongs to the thread from which they | |
143 // have registered. | |
144 base::MessageLoop message_loop_; | |
145 MockNetworkChangeNotifier network_change_notifier_; | |
146 scoped_refptr<base::TestMockTimeTaskRunner> task_runner_; | |
147 MockAffiliationFetchThrottlerDelegate mock_delegate_; | |
148 | |
149 DISALLOW_COPY_AND_ASSIGN(AffiliationFetchThrottlerTest); | |
150 }; | |
151 | |
152 TEST_F(AffiliationFetchThrottlerTest, SuccessfulRequests) { | |
153 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
154 | |
155 throttler->SignalNetworkRequestNeeded(); | |
156 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
157 | |
158 // Signal while request is in flight should be ignored. | |
159 throttler->SignalNetworkRequestNeeded(); | |
160 AssertNoReleaseUntilNoTasksRemain(); | |
161 throttler->InformOfNetworkRequestComplete(true); | |
162 AssertNoReleaseUntilNoTasksRemain(); | |
163 | |
164 // Duplicate the second signal 3 times: still only 1 callback should arrive. | |
165 throttler->SignalNetworkRequestNeeded(); | |
166 throttler->SignalNetworkRequestNeeded(); | |
167 throttler->SignalNetworkRequestNeeded(); | |
168 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
169 } | |
170 | |
171 TEST_F(AffiliationFetchThrottlerTest, FailedRequests) { | |
172 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
173 | |
174 throttler->SignalNetworkRequestNeeded(); | |
175 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
176 throttler->InformOfNetworkRequestComplete(false); | |
177 | |
178 // The request after the first failure should be delayed by |initial_delay_ms| | |
179 // spread out over Uniform(1 - |jitter_factor|, 1). | |
180 throttler->SignalNetworkRequestNeeded(); | |
181 const auto& kP = AffiliationFetchThrottler::kBackoffPolicy; | |
mmenke
2015/01/26 18:49:13
Per Google style guide, uncommon abbreviations sho
engedy
2015/01/26 19:39:35
Done.
| |
182 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween( | |
183 true, kP.initial_delay_ms * (1 - kP.jitter_factor), kP.initial_delay_ms)); | |
184 throttler->InformOfNetworkRequestComplete(true); | |
185 | |
186 // After a successful request, the next one should be released immediately. | |
187 throttler->SignalNetworkRequestNeeded(); | |
188 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
189 throttler->InformOfNetworkRequestComplete(false); | |
190 | |
191 // In general, the request after the n-th failure should be delayed by | |
192 // |multiply_factor| ^ (n-1) * |initial_delay_ms|, | |
193 // spread out over Uniform(1 - |jitter_factor|, 1), up until | |
194 // |maximum_backoff_ms| | |
195 // is reached. | |
196 for (size_t num_failures = 1; num_failures < 100; ++num_failures) { | |
197 throttler->SignalNetworkRequestNeeded(); | |
198 double max_delay_ms = | |
199 kP.initial_delay_ms * pow(kP.multiply_factor, num_failures - 1); | |
200 double min_delay_ms = max_delay_ms * (1 - kP.jitter_factor); | |
201 if (max_delay_ms > kP.maximum_backoff_ms) | |
202 max_delay_ms = kP.maximum_backoff_ms; | |
203 if (min_delay_ms > kP.maximum_backoff_ms) | |
204 min_delay_ms = kP.maximum_backoff_ms; | |
205 ASSERT_NO_FATAL_FAILURE( | |
206 AssertReleaseInBetween(true, min_delay_ms, max_delay_ms)); | |
207 throttler->InformOfNetworkRequestComplete(false); | |
208 } | |
209 } | |
210 | |
211 TEST_F(AffiliationFetchThrottlerTest, OnCanSendNetworkRequestReturnsFalse) { | |
212 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
213 | |
214 // A need for a network request is signaled, but as OnCanSendNetworkRequest() | |
215 // is called, the implementation returns false to indicate that the request | |
216 // will not be needed after all. InformOfNetworkRequestComplete() must not be | |
217 // called in this case. | |
218 throttler->SignalNetworkRequestNeeded(); | |
219 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(false, 0, 0)); | |
220 | |
221 // A subsequent signaling, however, should result in OnCanSendNetworkRequest() | |
222 // being called immediately. | |
223 throttler->SignalNetworkRequestNeeded(); | |
224 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
225 } | |
226 | |
227 TEST_F(AffiliationFetchThrottlerTest, GracePeriodAfterConnectivityIsRestored) { | |
228 SimulateHasNetworkConnectivity(false); | |
229 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
230 | |
231 // After connectivity is restored, the first request should be delayed by the | |
232 // grace period, spread out over Uniform(1 - |jitter_factor|, 1). | |
233 throttler->SignalNetworkRequestNeeded(); | |
234 AssertNoReleaseUntilNoTasksRemain(); | |
235 | |
236 SimulateHasNetworkConnectivity(true); | |
237 const auto& kP = AffiliationFetchThrottler::kBackoffPolicy; | |
238 const int64_t& kGraceMs = | |
239 AffiliationFetchThrottler::kGracePeriodAfterReconnectMs; | |
240 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween( | |
241 true, kGraceMs * (1 - kP.jitter_factor), kGraceMs)); | |
242 throttler->InformOfNetworkRequestComplete(true); | |
243 | |
244 // The next request should not be delayed. | |
245 throttler->SignalNetworkRequestNeeded(); | |
246 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
247 } | |
248 | |
249 // Same as GracePeriodAfterConnectivityIsRestored, but the network comes back | |
250 // just before SignalNetworkRequestNeeded() is called. | |
251 TEST_F(AffiliationFetchThrottlerTest, GracePeriodAfterConnectivityIsRestored2) { | |
252 SimulateHasNetworkConnectivity(false); | |
253 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
254 | |
255 SimulateHasNetworkConnectivity(true); | |
256 throttler->SignalNetworkRequestNeeded(); | |
257 const auto& kP = AffiliationFetchThrottler::kBackoffPolicy; | |
258 const int64_t& kGraceMs = | |
259 AffiliationFetchThrottler::kGracePeriodAfterReconnectMs; | |
260 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween( | |
261 true, kGraceMs * (1 - kP.jitter_factor), kGraceMs)); | |
262 throttler->InformOfNetworkRequestComplete(true); | |
263 | |
264 throttler->SignalNetworkRequestNeeded(); | |
265 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
266 } | |
267 | |
268 // Same as GracePeriodAfterConnectivityIsRestored, but the network goes down | |
269 // just as SignalNetworkRequestNeeded() is constructed. | |
270 TEST_F(AffiliationFetchThrottlerTest, GracePeriodAfterConnectivityIsRestored3) { | |
271 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
272 SimulateHasNetworkConnectivity(false); | |
273 | |
274 throttler->SignalNetworkRequestNeeded(); | |
275 AssertNoReleaseUntilNoTasksRemain(); | |
276 | |
277 SimulateHasNetworkConnectivity(true); | |
278 const auto& kP = AffiliationFetchThrottler::kBackoffPolicy; | |
279 const int64_t& kGraceMs = | |
280 AffiliationFetchThrottler::kGracePeriodAfterReconnectMs; | |
281 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween( | |
282 true, kGraceMs * (1 - kP.jitter_factor), kGraceMs)); | |
283 throttler->InformOfNetworkRequestComplete(true); | |
284 | |
285 throttler->SignalNetworkRequestNeeded(); | |
286 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
287 } | |
288 | |
289 TEST_F(AffiliationFetchThrottlerTest, ConnectivityLostDuringBackoff) { | |
290 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
291 | |
292 throttler->SignalNetworkRequestNeeded(); | |
293 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
294 throttler->InformOfNetworkRequestComplete(false); | |
295 | |
296 throttler->SignalNetworkRequestNeeded(); | |
297 SimulateHasNetworkConnectivity(false); | |
298 | |
299 // Let the exponential backoff delay expire, and verify nothing happens. | |
300 AssertNoReleaseUntilNoTasksRemain(); | |
301 | |
302 // Verify that the request is, however, sent after the normal grace period | |
303 // once connectivity is restored. | |
304 SimulateHasNetworkConnectivity(true); | |
305 const auto& kP = AffiliationFetchThrottler::kBackoffPolicy; | |
306 const int64_t& kGraceMs = | |
307 AffiliationFetchThrottler::kGracePeriodAfterReconnectMs; | |
308 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween( | |
309 true, kGraceMs * (1 - kP.jitter_factor), kGraceMs)); | |
310 throttler->InformOfNetworkRequestComplete(true); | |
311 } | |
312 | |
313 TEST_F(AffiliationFetchThrottlerTest, | |
314 ConnectivityLostAndRestoredDuringBackoff) { | |
315 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
316 | |
317 throttler->SignalNetworkRequestNeeded(); | |
318 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
319 throttler->InformOfNetworkRequestComplete(false); | |
320 | |
321 // Simulate 10 flakes, and run for a total of 5 grace periods. This verifies | |
322 // that a flaky connection will not flood the task queue with lots of of tasks | |
323 // and also that release will not happen while the connection is flaky even | |
324 // once the first grace period has expired. | |
325 throttler->SignalNetworkRequestNeeded(); | |
326 const auto& kP = AffiliationFetchThrottler::kBackoffPolicy; | |
327 const int64_t& kGraceMs = | |
328 AffiliationFetchThrottler::kGracePeriodAfterReconnectMs; | |
329 for (size_t t = 1; t <= 10; ++t) { | |
330 SimulateHasNetworkConnectivity(false); | |
331 AssertNoReleaseForMs(kGraceMs * (1 - kP.jitter_factor) / 2); | |
332 SimulateHasNetworkConnectivity(true); | |
333 } | |
334 EXPECT_EQ(1u, GetPendingTaskCount()); | |
335 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween( | |
336 true, kGraceMs * (1 - kP.jitter_factor), kGraceMs)); | |
337 } | |
338 | |
339 TEST_F(AffiliationFetchThrottlerTest, ConnectivityLostDuringRequest) { | |
340 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
341 | |
342 throttler->SignalNetworkRequestNeeded(); | |
343 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
344 | |
345 SimulateHasNetworkConnectivity(false); | |
346 AssertNoReleaseUntilNoTasksRemain(); | |
347 | |
348 throttler->InformOfNetworkRequestComplete(false); | |
349 AssertNoReleaseUntilNoTasksRemain(); | |
350 throttler->SignalNetworkRequestNeeded(); | |
351 AssertNoReleaseUntilNoTasksRemain(); | |
352 | |
353 SimulateHasNetworkConnectivity(true); | |
354 | |
355 // Verify that the next request is released after the grace period once | |
356 // connectivity is restored. | |
357 const auto& kP = AffiliationFetchThrottler::kBackoffPolicy; | |
358 const int64_t& kGraceMs = | |
359 AffiliationFetchThrottler::kGracePeriodAfterReconnectMs; | |
360 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween( | |
361 true, kGraceMs * (1 - kP.jitter_factor), kGraceMs)); | |
362 throttler->InformOfNetworkRequestComplete(true); | |
363 } | |
364 | |
365 TEST_F(AffiliationFetchThrottlerTest, | |
366 ConnectivityLostAndRestoredDuringRequest) { | |
367 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
368 | |
369 throttler->SignalNetworkRequestNeeded(); | |
370 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
371 | |
372 SimulateHasNetworkConnectivity(false); | |
373 AssertNoReleaseUntilNoTasksRemain(); | |
374 SimulateHasNetworkConnectivity(true); | |
375 throttler->InformOfNetworkRequestComplete(true); | |
376 AssertNoReleaseUntilNoTasksRemain(); | |
377 | |
378 // Even though the previous request succeeded, the next request should still | |
379 // be held back for the normal grace period after connection is restored. | |
380 throttler->SignalNetworkRequestNeeded(); | |
381 const auto& kP = AffiliationFetchThrottler::kBackoffPolicy; | |
382 const int64_t& kGraceMs = | |
383 AffiliationFetchThrottler::kGracePeriodAfterReconnectMs; | |
384 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween( | |
385 true, kGraceMs * (1 - kP.jitter_factor), kGraceMs)); | |
386 throttler->InformOfNetworkRequestComplete(true); | |
387 } | |
388 | |
389 TEST_F(AffiliationFetchThrottlerTest, InstanceDestroyedWhileInBackoff) { | |
390 scoped_ptr<AffiliationFetchThrottler> throttler(CreateThrottler()); | |
391 | |
392 throttler->SignalNetworkRequestNeeded(); | |
393 ASSERT_NO_FATAL_FAILURE(AssertReleaseInBetween(true, 0, 0)); | |
394 throttler->InformOfNetworkRequestComplete(false); | |
395 | |
396 throttler->SignalNetworkRequestNeeded(); | |
397 throttler.reset(); | |
398 EXPECT_EQ(1u, GetPendingTaskCount()); | |
399 AssertNoReleaseUntilNoTasksRemain(); | |
400 } | |
401 | |
402 } // namespace password_manager | |
OLD | NEW |