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.
5 #include "base/memory/memory_pressure_listener.h"
6 #include "base/run_loop.h"
7 #include "base/strings/stringprintf.h"
8 #include "base/test/simple_test_clock.h"
9 #include "net/base/net_log.h"
10 #include "net/base/sdch_manager.h"
11 #include "net/sdch/sdch_owner.h"
12 #include "net/url_request/url_request.h"
13 #include "net/url_request/url_request_context.h"
14 #include "net/url_request/url_request_error_job.h"
15 #include "net/url_request/url_request_job.h"
16 #include "net/url_request/url_request_job_factory.h"
17 #include "testing/gtest/include/gtest/gtest.h"
21 static const char generic_url
[] = "http://www.example.com";
22 static const char generic_domain
[] = "www.example.com";
24 static std::string
NewSdchDictionary(size_t dictionary_size
) {
25 std::string dictionary
;
26 dictionary
.append("Domain: ");
27 dictionary
.append(generic_domain
);
28 dictionary
.append("\n");
29 dictionary
.append("\n");
31 size_t original_dictionary_size
= dictionary
.size();
32 dictionary
.resize(dictionary_size
);
33 for (size_t i
= original_dictionary_size
; i
< dictionary_size
; ++i
)
34 dictionary
[i
] = static_cast<char>((i
% 127) + 1);
39 int outstanding_url_request_error_counting_jobs
= 0;
40 base::Closure
* empty_url_request_jobs_callback
= 0;
42 // Variation of URLRequestErrorJob to count number of outstanding
43 // instances and notify when that goes to zero.
44 class URLRequestErrorCountingJob
: public URLRequestJob
{
46 URLRequestErrorCountingJob(URLRequest
* request
,
47 NetworkDelegate
* network_delegate
,
49 : URLRequestJob(request
, network_delegate
),
52 ++outstanding_url_request_error_counting_jobs
;
55 void Start() override
{
56 base::MessageLoop::current()->PostTask(
57 FROM_HERE
, base::Bind(&URLRequestErrorCountingJob::StartAsync
,
58 weak_factory_
.GetWeakPtr()));
62 ~URLRequestErrorCountingJob() override
{
63 --outstanding_url_request_error_counting_jobs
;
64 if (0 == outstanding_url_request_error_counting_jobs
&&
65 empty_url_request_jobs_callback
) {
66 empty_url_request_jobs_callback
->Run();
71 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED
, error_
));
76 base::WeakPtrFactory
<URLRequestErrorCountingJob
> weak_factory_
;
78 DISALLOW_COPY_AND_ASSIGN(URLRequestErrorCountingJob
);
81 static int error_jobs_created
= 0;
83 class MockURLRequestJobFactory
: public URLRequestJobFactory
{
85 MockURLRequestJobFactory() {}
87 ~MockURLRequestJobFactory() override
{}
89 URLRequestJob
* MaybeCreateJobWithProtocolHandler(
90 const std::string
& scheme
,
92 NetworkDelegate
* network_delegate
) const override
{
94 return new URLRequestErrorCountingJob(request
, network_delegate
,
95 ERR_INTERNET_DISCONNECTED
);
98 URLRequestJob
* MaybeInterceptRedirect(URLRequest
* request
,
99 NetworkDelegate
* network_delegate
,
100 const GURL
& location
) const override
{
104 URLRequestJob
* MaybeInterceptResponse(
106 NetworkDelegate
* network_delegate
) const override
{
110 bool IsHandledProtocol(const std::string
& scheme
) const override
{
111 return scheme
== "http";
114 bool IsHandledURL(const GURL
& url
) const override
{
115 return url
.SchemeIs("http");
118 bool IsSafeRedirectTarget(const GURL
& location
) const override
{
123 class SdchOwnerTest
: public testing::Test
{
125 static const size_t kMaxSizeForTesting
= 1000 * 50;
126 static const size_t kMinFetchSpaceForTesting
= 500;
129 : last_jobs_created_(error_jobs_created
),
130 dictionary_creation_index_(0),
131 sdch_owner_(&sdch_manager_
, &url_request_context_
) {
132 // Any jobs created on this context will immediately error,
133 // which leaves the test in control of signals to SdchOwner.
134 url_request_context_
.set_job_factory(&job_factory_
);
136 // Reduce sizes to reduce time for string operations.
137 sdch_owner_
.SetMaxTotalDictionarySize(kMaxSizeForTesting
);
138 sdch_owner_
.SetMinSpaceForDictionaryFetch(kMinFetchSpaceForTesting
);
141 SdchManager
& sdch_manager() { return sdch_manager_
; }
142 SdchOwner
& sdch_owner() { return sdch_owner_
; }
143 BoundNetLog
& bound_net_log() { return net_log_
; }
145 int JobsRecentlyCreated() {
146 int result
= error_jobs_created
- last_jobs_created_
;
147 last_jobs_created_
= error_jobs_created
;
151 bool DictionaryPresentInManager(const std::string
& server_hash
) {
152 // Presumes all tests use generic url.
154 scoped_ptr
<SdchManager::DictionarySet
> set(
155 sdch_manager_
.GetDictionarySetByHash(GURL(generic_url
), server_hash
,
160 void SignalGetDictionaryAndClearJobs(GURL request_url
, GURL dictionary_url
) {
161 sdch_owner().OnGetDictionary(&sdch_manager_
, request_url
, dictionary_url
);
162 if (outstanding_url_request_error_counting_jobs
== 0)
164 base::RunLoop run_loop
;
165 base::Closure
quit_closure(run_loop
.QuitClosure());
166 empty_url_request_jobs_callback
= &quit_closure
;
168 empty_url_request_jobs_callback
= NULL
;
171 // Create a unique (by hash) dictionary of the given size,
172 // associate it with a unique URL, add it to the manager through
173 // SdchOwner::OnDictionaryFetched(), and return whether that
174 // addition was successful or not.
175 bool CreateAndAddDictionary(size_t size
, std::string
* server_hash_p
) {
177 base::StringPrintf("%s/d%d", generic_url
, dictionary_creation_index_
));
178 std::string
dictionary_text(NewSdchDictionary(size
- 4));
179 dictionary_text
+= base::StringPrintf("%04d", dictionary_creation_index_
);
180 ++dictionary_creation_index_
;
181 std::string client_hash
;
182 std::string server_hash
;
183 SdchManager::GenerateHash(dictionary_text
, &client_hash
, &server_hash
);
185 if (DictionaryPresentInManager(server_hash
))
187 sdch_owner().OnDictionaryFetched(dictionary_text
, dictionary_url
, net_log_
);
189 *server_hash_p
= server_hash
;
190 return DictionaryPresentInManager(server_hash
);
194 int last_jobs_created_
;
195 BoundNetLog net_log_
;
196 int dictionary_creation_index_
;
198 // The dependencies of these objects (sdch_owner_ -> {sdch_manager_,
199 // url_request_context_}, url_request_context_->job_factory_) require
200 // this order for correct destruction semantics.
201 MockURLRequestJobFactory job_factory_
;
202 URLRequestContext url_request_context_
;
203 SdchManager sdch_manager_
;
204 SdchOwner sdch_owner_
;
206 DISALLOW_COPY_AND_ASSIGN(SdchOwnerTest
);
209 // Does OnGetDictionary result in a fetch when there's enough space, and not
211 TEST_F(SdchOwnerTest
, OnGetDictionary_Fetching
) {
212 GURL
request_url(std::string(generic_url
) + "/r1");
214 // Fetch generated when empty.
215 GURL
dict_url1(std::string(generic_url
) + "/d1");
216 EXPECT_EQ(0, JobsRecentlyCreated());
217 SignalGetDictionaryAndClearJobs(request_url
, dict_url1
);
218 EXPECT_EQ(1, JobsRecentlyCreated());
220 // Fetch generated when half full.
221 GURL
dict_url2(std::string(generic_url
) + "/d2");
222 std::string
dictionary1(NewSdchDictionary(kMaxSizeForTesting
/ 2));
223 sdch_owner().OnDictionaryFetched(dictionary1
, dict_url1
, bound_net_log());
224 EXPECT_EQ(0, JobsRecentlyCreated());
225 SignalGetDictionaryAndClearJobs(request_url
, dict_url2
);
226 EXPECT_EQ(1, JobsRecentlyCreated());
228 // Fetch not generated when close to completely full.
229 GURL
dict_url3(std::string(generic_url
) + "/d3");
230 std::string
dictionary2(NewSdchDictionary(
231 (kMaxSizeForTesting
/ 2 - kMinFetchSpaceForTesting
/ 2)));
232 sdch_owner().OnDictionaryFetched(dictionary2
, dict_url2
, bound_net_log());
233 EXPECT_EQ(0, JobsRecentlyCreated());
234 SignalGetDictionaryAndClearJobs(request_url
, dict_url3
);
235 EXPECT_EQ(0, JobsRecentlyCreated());
238 // Make sure attempts to add dictionaries do what they should.
239 TEST_F(SdchOwnerTest
, OnDictionaryFetched_Fetching
) {
240 GURL
request_url(std::string(generic_url
) + "/r1");
241 std::string client_hash
;
242 std::string server_hash
;
244 // Add successful when empty.
245 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, nullptr));
246 EXPECT_EQ(0, JobsRecentlyCreated());
248 // Add successful when half full.
249 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, nullptr));
250 EXPECT_EQ(0, JobsRecentlyCreated());
252 // Add unsuccessful when full.
253 EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, nullptr));
254 EXPECT_EQ(0, JobsRecentlyCreated());
257 // Confirm auto-eviction happens if space is needed.
258 TEST_F(SdchOwnerTest
, ConfirmAutoEviction
) {
259 std::string server_hash_d1
;
260 std::string server_hash_d2
;
261 std::string server_hash_d3
;
263 // Add two dictionaries, one recent, one more than a day in the past.
264 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d1
));
266 scoped_ptr
<base::SimpleTestClock
> clock(new base::SimpleTestClock
);
267 clock
->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
268 sdch_owner().SetClockForTesting(clock
.Pass());
270 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d2
));
272 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
273 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
275 // The addition of a new dictionary should succeed, evicting the old one.
276 clock
.reset(new base::SimpleTestClock
);
277 clock
->SetNow(base::Time::Now());
278 sdch_owner().SetClockForTesting(clock
.Pass());
280 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d3
));
281 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
282 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
283 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
286 // Confirm auto-eviction happens if space is needed, with a more complicated
288 TEST_F(SdchOwnerTest
, ConfirmAutoEviction_2
) {
289 std::string server_hash_d1
;
290 std::string server_hash_d2
;
291 std::string server_hash_d3
;
293 // Add dictionaries, one recent, two more than a day in the past that
294 // between them add up to the space needed.
295 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d1
));
297 scoped_ptr
<base::SimpleTestClock
> clock(new base::SimpleTestClock
);
298 clock
->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
299 sdch_owner().SetClockForTesting(clock
.Pass());
301 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d2
));
302 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d3
));
304 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
305 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
306 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
308 // The addition of a new dictionary should succeed, evicting the old one.
309 clock
.reset(new base::SimpleTestClock
);
310 clock
->SetNow(base::Time::Now());
311 sdch_owner().SetClockForTesting(clock
.Pass());
313 std::string server_hash_d4
;
314 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d4
));
315 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
316 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
317 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d3
));
318 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4
));
321 // Confirm if only one dictionary needs to be evicted it's the oldest.
322 TEST_F(SdchOwnerTest
, ConfirmAutoEviction_Oldest
) {
323 std::string server_hash_d1
;
324 std::string server_hash_d2
;
325 std::string server_hash_d3
;
327 // Add dictionaries, one recent, one two days in the past, and one
328 // four days in the past.
329 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d1
));
331 scoped_ptr
<base::SimpleTestClock
> clock(new base::SimpleTestClock
);
332 clock
->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
333 sdch_owner().SetClockForTesting(clock
.Pass());
334 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d2
));
336 clock
.reset(new base::SimpleTestClock
);
337 clock
->SetNow(base::Time::Now() - base::TimeDelta::FromDays(4));
338 sdch_owner().SetClockForTesting(clock
.Pass());
339 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d3
));
341 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
342 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
343 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
345 // The addition of a new dictionary should succeed, evicting only the
347 clock
.reset(new base::SimpleTestClock
);
348 clock
->SetNow(base::Time::Now());
349 sdch_owner().SetClockForTesting(clock
.Pass());
351 std::string server_hash_d4
;
352 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d4
));
353 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
354 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
355 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d3
));
356 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4
));
359 // Confirm using a dictionary changes eviction behavior properly.
360 TEST_F(SdchOwnerTest
, UseChangesEviction
) {
361 std::string server_hash_d1
;
362 std::string server_hash_d2
;
363 std::string server_hash_d3
;
365 // Add dictionaries, one recent, one two days in the past, and one
366 // four days in the past.
367 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d1
));
369 scoped_ptr
<base::SimpleTestClock
> clock(new base::SimpleTestClock
);
370 clock
->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
371 sdch_owner().SetClockForTesting(clock
.Pass());
372 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d2
));
374 clock
.reset(new base::SimpleTestClock
);
375 clock
->SetNow(base::Time::Now() - base::TimeDelta::FromDays(4));
376 sdch_owner().SetClockForTesting(clock
.Pass());
377 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d3
));
379 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
380 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
381 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
383 clock
.reset(new base::SimpleTestClock
);
384 clock
->SetNow(base::Time::Now());
385 sdch_owner().SetClockForTesting(clock
.Pass());
387 // Use the oldest dictionary.
388 sdch_owner().OnDictionaryUsed(&sdch_manager(), server_hash_d3
);
390 // The addition of a new dictionary should succeed, evicting only the
392 std::string server_hash_d4
;
393 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d4
));
394 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
395 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
396 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
397 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4
));
400 // Confirm using a dictionary can prevent the addition of a new dictionary.
401 TEST_F(SdchOwnerTest
, UsePreventsAddition
) {
402 std::string server_hash_d1
;
403 std::string server_hash_d2
;
404 std::string server_hash_d3
;
406 // Add dictionaries, one recent, one two days in the past, and one
407 // four days in the past.
408 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d1
));
410 scoped_ptr
<base::SimpleTestClock
> clock(new base::SimpleTestClock
);
411 clock
->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
412 sdch_owner().SetClockForTesting(clock
.Pass());
413 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d2
));
415 clock
.reset(new base::SimpleTestClock
);
416 clock
->SetNow(base::Time::Now() - base::TimeDelta::FromDays(4));
417 sdch_owner().SetClockForTesting(clock
.Pass());
418 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d3
));
420 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
421 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
422 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
424 clock
.reset(new base::SimpleTestClock
);
425 clock
->SetNow(base::Time::Now());
426 sdch_owner().SetClockForTesting(clock
.Pass());
428 // Use the older dictionaries.
429 sdch_owner().OnDictionaryUsed(&sdch_manager(), server_hash_d2
);
430 sdch_owner().OnDictionaryUsed(&sdch_manager(), server_hash_d3
);
432 // The addition of a new dictionary should fail, not evicting anything.
433 std::string server_hash_d4
;
434 EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d4
));
435 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
436 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
437 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
438 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d4
));
441 // Confirm clear gets all the space back.
442 TEST_F(SdchOwnerTest
, ClearReturnsSpace
) {
443 std::string server_hash_d1
;
444 std::string server_hash_d2
;
446 // Take up all the space.
447 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
, &server_hash_d1
));
449 // Addition should fail.
450 EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting
, &server_hash_d2
));
452 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
453 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
455 sdch_manager().ClearData();
456 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d1
));
457 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
459 // Addition should now succeed.
460 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
, nullptr));
463 // Confirm memory pressure gets all the space back.
464 TEST_F(SdchOwnerTest
, MemoryPressureReturnsSpace
) {
465 std::string server_hash_d1
;
466 std::string server_hash_d2
;
468 // Take up all the space.
469 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
, &server_hash_d1
));
471 // Addition should fail.
472 EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting
, &server_hash_d2
));
474 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
475 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
477 base::MemoryPressureListener::NotifyMemoryPressure(
478 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE
);
479 // The notification may have (implementation note: does :-}) use a PostTask,
480 // so we drain the local message queue. This should be safe (i.e. not have
481 // an inifinite number of messages) in a unit test.
482 base::RunLoop().RunUntilIdle();
484 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d1
));
485 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
487 // Addition should now succeed.
488 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
, nullptr));