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/location.h"
6 #include "base/memory/memory_pressure_listener.h"
7 #include "base/prefs/testing_pref_store.h"
8 #include "base/run_loop.h"
9 #include "base/single_thread_task_runner.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/test/histogram_tester.h"
12 #include "base/test/simple_test_clock.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "base/values.h"
15 #include "net/base/sdch_manager.h"
16 #include "net/log/net_log.h"
17 #include "net/sdch/sdch_owner.h"
18 #include "net/url_request/url_request.h"
19 #include "net/url_request/url_request_context.h"
20 #include "net/url_request/url_request_error_job.h"
21 #include "net/url_request/url_request_job.h"
22 #include "net/url_request/url_request_job_factory.h"
23 #include "net/url_request/url_request_test_util.h"
24 #include "testing/gtest/include/gtest/gtest.h"
28 bool GetDictionaryForURL(TestingPrefStore
* store
,
31 base::DictionaryValue
** dict
) {
32 base::Value
* sdch_val
= nullptr;
33 base::DictionaryValue
* sdch_dict
= nullptr;
34 if (!store
->GetMutableValue("SDCH", &sdch_val
))
36 if (!sdch_val
->GetAsDictionary(&sdch_dict
))
39 base::DictionaryValue
* dicts_dict
= nullptr;
40 if (!sdch_dict
->GetDictionary("dictionaries", &dicts_dict
))
43 base::DictionaryValue::Iterator
it(*dicts_dict
);
44 while (!it
.IsAtEnd()) {
45 const base::DictionaryValue
* d
= nullptr;
46 if (!it
.value().GetAsDictionary(&d
))
49 if (d
->GetString("url", &dict_url
) && dict_url
== url
.spec()) {
53 dicts_dict
->GetDictionary(it
.key(), dict
);
66 static const char generic_url
[] = "http://www.example.com";
67 static const char generic_domain
[] = "www.example.com";
69 static std::string
NewSdchDictionary(size_t dictionary_size
) {
70 std::string dictionary
;
71 dictionary
.append("Domain: ");
72 dictionary
.append(generic_domain
);
73 dictionary
.append("\n");
74 dictionary
.append("\n");
76 size_t original_dictionary_size
= dictionary
.size();
77 dictionary
.resize(dictionary_size
);
78 for (size_t i
= original_dictionary_size
; i
< dictionary_size
; ++i
)
79 dictionary
[i
] = static_cast<char>((i
% 127) + 1);
84 int outstanding_url_request_error_counting_jobs
= 0;
85 base::Closure
* empty_url_request_jobs_callback
= 0;
87 // Variation of URLRequestErrorJob to count number of outstanding
88 // instances and notify when that goes to zero.
89 class URLRequestErrorCountingJob
: public URLRequestJob
{
91 URLRequestErrorCountingJob(URLRequest
* request
,
92 NetworkDelegate
* network_delegate
,
94 : URLRequestJob(request
, network_delegate
),
97 ++outstanding_url_request_error_counting_jobs
;
100 void Start() override
{
101 base::ThreadTaskRunnerHandle::Get()->PostTask(
102 FROM_HERE
, base::Bind(&URLRequestErrorCountingJob::StartAsync
,
103 weak_factory_
.GetWeakPtr()));
107 ~URLRequestErrorCountingJob() override
{
108 --outstanding_url_request_error_counting_jobs
;
109 if (0 == outstanding_url_request_error_counting_jobs
&&
110 empty_url_request_jobs_callback
) {
111 empty_url_request_jobs_callback
->Run();
116 NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED
, error_
));
121 base::WeakPtrFactory
<URLRequestErrorCountingJob
> weak_factory_
;
123 DISALLOW_COPY_AND_ASSIGN(URLRequestErrorCountingJob
);
126 static int error_jobs_created
= 0;
128 class MockURLRequestJobFactory
: public URLRequestJobFactory
{
130 MockURLRequestJobFactory() {}
132 ~MockURLRequestJobFactory() override
{}
134 URLRequestJob
* MaybeCreateJobWithProtocolHandler(
135 const std::string
& scheme
,
137 NetworkDelegate
* network_delegate
) const override
{
138 ++error_jobs_created
;
139 return new URLRequestErrorCountingJob(request
, network_delegate
,
140 ERR_INTERNET_DISCONNECTED
);
143 URLRequestJob
* MaybeInterceptRedirect(URLRequest
* request
,
144 NetworkDelegate
* network_delegate
,
145 const GURL
& location
) const override
{
149 URLRequestJob
* MaybeInterceptResponse(
151 NetworkDelegate
* network_delegate
) const override
{
155 bool IsHandledProtocol(const std::string
& scheme
) const override
{
156 return scheme
== "http";
159 bool IsHandledURL(const GURL
& url
) const override
{
160 return url
.SchemeIs("http");
163 bool IsSafeRedirectTarget(const GURL
& location
) const override
{
168 class MockSdchDictionaryFetcher
: public SdchDictionaryFetcher
{
170 MockSdchDictionaryFetcher() : SdchDictionaryFetcher(&test_context_
) {}
171 ~MockSdchDictionaryFetcher() {}
173 struct PendingRequest
{
174 PendingRequest(const GURL
& url
,
175 const OnDictionaryFetchedCallback
& callback
)
176 : url_(url
), callback_(callback
) {}
178 OnDictionaryFetchedCallback callback_
;
181 virtual bool Schedule(const GURL
& dictionary_url
,
182 const OnDictionaryFetchedCallback
& callback
) {
183 if (!HasPendingRequest(dictionary_url
)) {
184 requests_
.push_back(PendingRequest(dictionary_url
, callback
));
190 virtual bool ScheduleReload(const GURL
& dictionary_url
,
191 const OnDictionaryFetchedCallback
& callback
) {
192 if (!HasPendingRequest(dictionary_url
)) {
193 requests_
.push_back(PendingRequest(dictionary_url
, callback
));
199 virtual void Cancel() {
203 bool HasPendingRequest(const GURL
& dictionary_url
) {
204 for (std::vector
<PendingRequest
>::iterator it
= requests_
.begin();
205 it
!= requests_
.end(); ++it
) {
206 if (it
->url_
== dictionary_url
)
212 bool CompletePendingRequest(const GURL
& dictionary_url
,
213 const std::string
& dictionary_text
,
214 const BoundNetLog
& net_log
,
215 bool was_from_cache
) {
216 for (std::vector
<PendingRequest
>::iterator it
= requests_
.begin();
217 it
!= requests_
.end(); ++it
) {
218 if (it
->url_
== dictionary_url
) {
219 it
->callback_
.Run(dictionary_text
, dictionary_url
, net_log
,
229 TestURLRequestContext test_context_
;
230 std::vector
<PendingRequest
> requests_
;
231 DISALLOW_COPY_AND_ASSIGN(MockSdchDictionaryFetcher
);
234 // File testing infrastructure summary:
235 // * NewSdchDictionary(): Creates a dictionary of a specific size.
236 // * URLRequestErrorCountingJob: A URLRequestJob that returns an error
237 // and counts the number of outstanding (started but not finished)
238 // jobs, and calls a global callback when that number transitions to zero.
239 // * MockURLRequestJobFactory: Factory to create the above jobs. Tracks
240 // the number of jobs created.
241 // * SdchOwnerTest: Interfaces
242 // * Access manager, owner, and net log
243 // * Return the number of jobs created in a time interval
244 // * Return dictionary present in the manager
245 // * Notify SdchOwner of an incoming dictionary (& wait until jobs clear)
246 // * Attempt to add a dictionary and test for success.
248 // * Let the owner know about a Get-Dictionary header and test for
249 // appropriate jobs being created.
250 // * Let the owner know that a dictionary was successfully fetched
251 // and test for appropriate outcome.
252 // * Either of the above, having previously added dictionaries to create
253 // a particular initial state.
254 class SdchOwnerTest
: public testing::Test
{
256 static const size_t kMaxSizeForTesting
= 1000 * 50;
257 static const size_t kMinFetchSpaceForTesting
= 500;
260 : last_jobs_created_(error_jobs_created
),
261 dictionary_creation_index_(0),
262 pref_store_(new TestingPrefStore
),
263 sdch_owner_(new SdchOwner(&sdch_manager_
, &url_request_context_
)) {
264 // Any jobs created on this context will immediately error,
265 // which leaves the test in control of signals to SdchOwner.
266 url_request_context_
.set_job_factory(&job_factory_
);
268 // Reduce sizes to reduce time for string operations.
269 sdch_owner_
->SetMaxTotalDictionarySize(kMaxSizeForTesting
);
270 sdch_owner_
->SetMinSpaceForDictionaryFetch(kMinFetchSpaceForTesting
);
273 SdchManager
& sdch_manager() { return sdch_manager_
; }
274 SdchOwner
& sdch_owner() { return *(sdch_owner_
.get()); }
275 BoundNetLog
& bound_net_log() { return net_log_
; }
276 TestingPrefStore
& pref_store() { return *(pref_store_
.get()); }
278 int JobsRecentlyCreated() {
279 int result
= error_jobs_created
- last_jobs_created_
;
280 last_jobs_created_
= error_jobs_created
;
284 bool DictionaryPresentInManager(const std::string
& server_hash
) {
285 // Presumes all tests use generic url.
287 scoped_ptr
<SdchManager::DictionarySet
> set(
288 sdch_manager_
.GetDictionarySetByHash(GURL(generic_url
), server_hash
,
293 void WaitForNoJobs() {
294 if (outstanding_url_request_error_counting_jobs
== 0)
297 base::RunLoop run_loop
;
298 base::Closure
quit_closure(run_loop
.QuitClosure());
299 empty_url_request_jobs_callback
= &quit_closure
;
301 empty_url_request_jobs_callback
= NULL
;
304 void SignalGetDictionaryAndClearJobs(GURL request_url
, GURL dictionary_url
) {
305 sdch_owner().OnGetDictionary(request_url
, dictionary_url
);
309 // Create a unique (by hash) dictionary of the given size,
310 // associate it with a unique URL, add it to the manager through
311 // SdchOwner::OnDictionaryFetched(), and return whether that
312 // addition was successful or not.
313 bool CreateAndAddDictionary(size_t size
,
314 std::string
* server_hash_p
,
315 base::Time last_used_time
) {
317 base::StringPrintf("%s/d%d", generic_url
, dictionary_creation_index_
));
318 std::string
dictionary_text(NewSdchDictionary(size
- 4));
319 dictionary_text
+= base::StringPrintf("%04d", dictionary_creation_index_
);
320 ++dictionary_creation_index_
;
321 std::string client_hash
;
322 std::string server_hash
;
323 SdchManager::GenerateHash(dictionary_text
, &client_hash
, &server_hash
);
325 if (DictionaryPresentInManager(server_hash
))
327 sdch_owner().OnDictionaryFetched(last_used_time
, 0, dictionary_text
,
328 dictionary_url
, net_log_
, false);
330 *server_hash_p
= server_hash
;
331 return DictionaryPresentInManager(server_hash
);
335 sdch_owner_
.reset(new SdchOwner(&sdch_manager_
, &url_request_context_
));
339 int last_jobs_created_
;
340 BoundNetLog net_log_
;
341 int dictionary_creation_index_
;
343 // The dependencies of these objects (sdch_owner_ -> {sdch_manager_,
344 // url_request_context_}, url_request_context_->job_factory_) require
345 // this order for correct destruction semantics.
346 MockURLRequestJobFactory job_factory_
;
347 URLRequestContext url_request_context_
;
348 SdchManager sdch_manager_
;
349 scoped_refptr
<TestingPrefStore
> pref_store_
;
350 scoped_ptr
<SdchOwner
> sdch_owner_
;
352 DISALLOW_COPY_AND_ASSIGN(SdchOwnerTest
);
355 // Does OnGetDictionary result in a fetch when there's enough space, and not
357 TEST_F(SdchOwnerTest
, OnGetDictionary_Fetching
) {
358 GURL
request_url(std::string(generic_url
) + "/r1");
360 // Fetch generated when empty.
361 GURL
dict_url1(std::string(generic_url
) + "/d1");
362 EXPECT_EQ(0, JobsRecentlyCreated());
363 SignalGetDictionaryAndClearJobs(request_url
, dict_url1
);
364 EXPECT_EQ(1, JobsRecentlyCreated());
366 // Fetch generated when half full.
367 GURL
dict_url2(std::string(generic_url
) + "/d2");
368 std::string
dictionary1(NewSdchDictionary(kMaxSizeForTesting
/ 2));
369 sdch_owner().OnDictionaryFetched(base::Time::Now(), 1, dictionary1
,
370 dict_url1
, bound_net_log(), false);
371 EXPECT_EQ(0, JobsRecentlyCreated());
372 SignalGetDictionaryAndClearJobs(request_url
, dict_url2
);
373 EXPECT_EQ(1, JobsRecentlyCreated());
375 // Fetch not generated when close to completely full.
376 GURL
dict_url3(std::string(generic_url
) + "/d3");
377 std::string
dictionary2(NewSdchDictionary(
378 (kMaxSizeForTesting
/ 2 - kMinFetchSpaceForTesting
/ 2)));
379 sdch_owner().OnDictionaryFetched(base::Time::Now(), 1, dictionary2
,
380 dict_url2
, bound_net_log(), false);
381 EXPECT_EQ(0, JobsRecentlyCreated());
382 SignalGetDictionaryAndClearJobs(request_url
, dict_url3
);
383 EXPECT_EQ(0, JobsRecentlyCreated());
386 // Make sure attempts to add dictionaries do what they should.
387 TEST_F(SdchOwnerTest
, OnDictionaryFetched_Fetching
) {
388 GURL
request_url(std::string(generic_url
) + "/r1");
389 std::string client_hash
;
390 std::string server_hash
;
392 // In the past, but still fresh for an unused dictionary.
393 base::Time
dictionary_last_used_time(base::Time::Now() -
394 base::TimeDelta::FromMinutes(30));
396 // Add successful when empty.
397 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, nullptr,
398 dictionary_last_used_time
));
399 EXPECT_EQ(0, JobsRecentlyCreated());
401 // Add successful when half full.
402 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, nullptr,
403 dictionary_last_used_time
));
404 EXPECT_EQ(0, JobsRecentlyCreated());
406 // Add unsuccessful when full.
407 EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, nullptr,
408 dictionary_last_used_time
));
409 EXPECT_EQ(0, JobsRecentlyCreated());
412 // Confirm auto-eviction happens if space is needed.
413 TEST_F(SdchOwnerTest
, ConfirmAutoEviction
) {
414 base::Time start_time
= base::Time::Now();
415 std::string server_hash_d1
;
416 std::string server_hash_d2
;
417 std::string server_hash_d3
;
419 base::SimpleTestClock
* test_clock
= new base::SimpleTestClock();
420 sdch_owner().SetClockForTesting(make_scoped_ptr(test_clock
));
421 test_clock
->SetNow(base::Time::Now());
423 // Add two dictionaries, one recent, one more than a day in the past.
424 base::Time
fresh(base::Time::Now() - base::TimeDelta::FromHours(23));
425 base::Time
stale(base::Time::Now() - base::TimeDelta::FromHours(25));
428 CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d1
, fresh
));
430 CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d2
, stale
));
432 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
433 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
435 base::HistogramTester tester
;
436 const base::TimeDelta synthetic_delta
= base::TimeDelta::FromSeconds(5);
438 test_clock
->Advance(synthetic_delta
);
441 CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d3
, fresh
));
442 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
443 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
444 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
446 base::TimeDelta expected_proc_lifetime
= synthetic_delta
* 3 +
447 base::Time::Now() - start_time
;
448 size_t expected_value_base
= ((kMaxSizeForTesting
/ 2) *
449 synthetic_delta
.InMilliseconds()) /
450 expected_proc_lifetime
.InMilliseconds();
452 const char *kHistogram
= "Sdch3.TimeWeightedMemoryUse";
453 tester
.ExpectTotalCount(kHistogram
, 0);
455 // Dictionary insertions and deletions:
456 // T = 0: insert d1 and d2
457 // T = 5: insert d3, which evicts d2
458 // T = 15: destroy SdchOwner, which evicts d1 and d3
459 // Therefore, d2's lifetime is synthetic_delta, d1's is synthetic_delta * 3,
460 // and d3's is synthetic_delta * 2. The expected_value_base variable is the
461 // base factor for d2's memory consumption, of which d1's and d3's are
463 test_clock
->Advance(synthetic_delta
* 2);
466 tester
.ExpectTotalCount(kHistogram
, 3);
467 tester
.ExpectBucketCount(kHistogram
, expected_value_base
, 1);
468 tester
.ExpectBucketCount(kHistogram
, expected_value_base
* 2, 1);
469 tester
.ExpectBucketCount(kHistogram
, expected_value_base
* 3, 1);
472 // Confirm auto-eviction happens if space is needed, with a more complicated
474 TEST_F(SdchOwnerTest
, ConfirmAutoEviction_2
) {
475 std::string server_hash_d1
;
476 std::string server_hash_d2
;
477 std::string server_hash_d3
;
479 // Add dictionaries, one recent, two more than a day in the past that
480 // between them add up to the space needed.
481 base::Time
fresh(base::Time::Now() - base::TimeDelta::FromHours(23));
482 base::Time
stale(base::Time::Now() - base::TimeDelta::FromHours(25));
484 CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d1
, fresh
));
487 CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d2
, stale
));
489 CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d3
, stale
));
491 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
492 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
493 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
495 std::string server_hash_d4
;
497 CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d4
, fresh
));
498 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
499 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
500 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d3
));
501 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4
));
504 // Confirm if only one dictionary needs to be evicted it's the oldest.
505 TEST_F(SdchOwnerTest
, ConfirmAutoEviction_Oldest
) {
506 std::string server_hash_d1
;
507 std::string server_hash_d2
;
508 std::string server_hash_d3
;
510 // Add dictionaries, one recent, one two days in the past, and one
511 // four days in the past.
512 base::Time
fresh(base::Time::Now() - base::TimeDelta::FromHours(23));
513 base::Time
stale_newer(base::Time::Now() - base::TimeDelta::FromHours(47));
514 base::Time
stale_older(base::Time::Now() - base::TimeDelta::FromHours(71));
517 CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d1
, fresh
));
519 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d2
,
522 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d3
,
525 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
526 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
527 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
529 // The addition of a new dictionary should succeed, evicting only the
532 std::string server_hash_d4
;
534 CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d4
, fresh
));
535 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
536 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
537 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d3
));
538 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4
));
541 // Confirm using a dictionary changes eviction behavior properly.
542 TEST_F(SdchOwnerTest
, UseChangesEviction
) {
543 std::string server_hash_d1
;
544 std::string server_hash_d2
;
545 std::string server_hash_d3
;
547 // Add dictionaries, one recent, one two days in the past, and one
548 // four days in the past.
549 base::Time
fresh(base::Time::Now() - base::TimeDelta::FromHours(23));
550 base::Time
stale_newer(base::Time::Now() - base::TimeDelta::FromHours(47));
551 base::Time
stale_older(base::Time::Now() - base::TimeDelta::FromHours(71));
554 CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d1
, fresh
));
556 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d2
,
559 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d3
,
562 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
563 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
564 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
566 // Use the oldest dictionary.
567 sdch_owner().OnDictionaryUsed(server_hash_d3
);
569 // The addition of a new dictionary should succeed, evicting only the
571 std::string server_hash_d4
;
573 CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d4
, fresh
));
574 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
575 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
576 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
577 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4
));
580 // Confirm using a dictionary can prevent the addition of a new dictionary.
581 TEST_F(SdchOwnerTest
, UsePreventsAddition
) {
582 std::string server_hash_d1
;
583 std::string server_hash_d2
;
584 std::string server_hash_d3
;
586 // Add dictionaries, one recent, one two days in the past, and one
587 // four days in the past.
588 base::Time
fresh(base::Time::Now() - base::TimeDelta::FromMinutes(30));
589 base::Time
stale_newer(base::Time::Now() - base::TimeDelta::FromHours(47));
590 base::Time
stale_older(base::Time::Now() - base::TimeDelta::FromHours(71));
593 CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d1
, fresh
));
595 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d2
,
598 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 4, &server_hash_d3
,
601 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
602 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
603 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
605 // Use the older dictionaries.
606 sdch_owner().OnDictionaryUsed(server_hash_d2
);
607 sdch_owner().OnDictionaryUsed(server_hash_d3
);
609 // The addition of a new dictionary should fail, not evicting anything.
610 std::string server_hash_d4
;
612 CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d4
, fresh
));
613 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
614 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2
));
615 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3
));
616 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d4
));
619 // Confirm clear gets all the space back.
620 TEST_F(SdchOwnerTest
, ClearReturnsSpace
) {
621 std::string server_hash_d1
;
622 std::string server_hash_d2
;
624 // Take up all the space.
625 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
, &server_hash_d1
,
627 // Addition should fail.
628 EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting
, &server_hash_d2
,
630 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
631 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
632 sdch_manager().ClearData();
633 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d1
));
634 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
636 // Addition should now succeed.
638 CreateAndAddDictionary(kMaxSizeForTesting
, nullptr, base::Time::Now()));
641 // Confirm memory pressure gets all the space back.
642 TEST_F(SdchOwnerTest
, MemoryPressureReturnsSpace
) {
643 std::string server_hash_d1
;
644 std::string server_hash_d2
;
646 // Take up all the space.
647 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
, &server_hash_d1
,
650 // Addition should fail.
651 EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting
, &server_hash_d2
,
654 EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1
));
655 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
657 base::MemoryPressureListener::NotifyMemoryPressure(
658 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE
);
659 // The notification may have (implementation note: does :-}) use a PostTask,
660 // so we drain the local message queue. This should be safe (i.e. not have
661 // an inifinite number of messages) in a unit test.
662 base::RunLoop().RunUntilIdle();
664 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d1
));
665 EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2
));
667 // Addition should now succeed.
669 CreateAndAddDictionary(kMaxSizeForTesting
, nullptr, base::Time::Now()));
672 // Confirm that use of a pinned dictionary after its removal works properly.
673 TEST_F(SdchOwnerTest
, PinRemoveUse
) {
674 pref_store().SetInitializationCompleted();
675 sdch_owner().EnablePersistentStorage(&pref_store());
677 std::string server_hash_d1
;
678 EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting
/ 2, &server_hash_d1
,
681 scoped_ptr
<SdchManager::DictionarySet
> return_set(
682 sdch_manager().GetDictionarySet(
683 GURL(std::string(generic_url
) + "/x.html")));
684 ASSERT_TRUE(return_set
.get());
685 EXPECT_TRUE(return_set
->GetDictionaryText(server_hash_d1
));
687 const base::Value
* result
= nullptr;
688 const base::DictionaryValue
* dict_result
= nullptr;
689 ASSERT_TRUE(pref_store().GetValue("SDCH", &result
));
690 ASSERT_TRUE(result
->GetAsDictionary(&dict_result
));
691 EXPECT_TRUE(dict_result
->Get("dictionaries", &result
));
692 EXPECT_TRUE(dict_result
->Get("dictionaries." + server_hash_d1
, &result
));
694 sdch_manager().ClearData();
696 ASSERT_TRUE(pref_store().GetValue("SDCH", &result
));
697 ASSERT_TRUE(result
->GetAsDictionary(&dict_result
));
698 EXPECT_TRUE(dict_result
->Get("dictionaries", &result
));
699 EXPECT_FALSE(dict_result
->Get("dictionaries." + server_hash_d1
, &result
));
701 scoped_ptr
<SdchManager::DictionarySet
> return_set2(
702 sdch_manager().GetDictionarySet(
703 GURL(std::string(generic_url
) + "/x.html")));
704 EXPECT_FALSE(return_set2
.get());
706 sdch_manager().OnDictionaryUsed(server_hash_d1
);
708 ASSERT_TRUE(pref_store().GetValue("SDCH", &result
));
709 ASSERT_TRUE(result
->GetAsDictionary(&dict_result
));
710 EXPECT_TRUE(dict_result
->Get("dictionaries", &result
));
711 EXPECT_FALSE(dict_result
->Get("dictionaries." + server_hash_d1
, &result
));
714 class SdchOwnerPersistenceTest
: public ::testing::Test
{
716 SdchOwnerPersistenceTest() : pref_store_(new TestingPrefStore()) {
717 pref_store_
->SetInitializationCompleted();
719 virtual ~SdchOwnerPersistenceTest() {}
725 void ResetOwner(bool delay
) {
726 // This has to be done first, since SdchOwner may be observing SdchManager,
727 // and SdchManager can't be destroyed with a live observer.
729 manager_
.reset(new SdchManager());
730 fetcher_
= new MockSdchDictionaryFetcher();
731 owner_
.reset(new SdchOwner(manager_
.get(),
732 &url_request_context_
));
733 owner_
->SetMaxTotalDictionarySize(SdchOwnerTest::kMaxSizeForTesting
);
734 owner_
->SetMinSpaceForDictionaryFetch(
735 SdchOwnerTest::kMinFetchSpaceForTesting
);
736 owner_
->SetFetcherForTesting(make_scoped_ptr(fetcher_
));
738 owner_
->EnablePersistentStorage(pref_store_
.get());
741 void InsertDictionaryForURL(const GURL
& url
, const std::string
& nonce
) {
742 owner_
->OnDictionaryFetched(base::Time::Now(), 1,
743 CreateDictionary(url
, nonce
),
744 url
, net_log_
, false);
747 bool CompleteLoadFromURL(const GURL
& url
, const std::string
& nonce
,
748 bool was_from_cache
) {
749 return fetcher_
->CompletePendingRequest(url
, CreateDictionary(url
, nonce
),
750 net_log_
, was_from_cache
);
753 std::string
CreateDictionary(const GURL
& url
, const std::string
& nonce
) {
755 dict
.append("Domain: ");
756 dict
.append(url
.host());
758 dict
.append(url
.spec());
764 BoundNetLog net_log_
;
765 scoped_refptr
<TestingPrefStore
> pref_store_
;
766 scoped_ptr
<SdchManager
> manager_
;
767 MockSdchDictionaryFetcher
* fetcher_
;
768 scoped_ptr
<SdchOwner
> owner_
;
769 TestURLRequestContext url_request_context_
;
772 // Test an empty persistence store.
773 TEST_F(SdchOwnerPersistenceTest
, Empty
) {
775 EXPECT_EQ(0, owner_
->GetDictionaryCountForTesting());
778 // Test a persistence store with an empty dictionary.
779 TEST_F(SdchOwnerPersistenceTest
, Persistent_EmptyDict
) {
780 pref_store_
->SetValue("SDCH", make_scoped_ptr(new base::DictionaryValue()),
781 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS
);
783 EXPECT_EQ(0, owner_
->GetDictionaryCountForTesting());
786 // Test a persistence store with a bad version number.
787 TEST_F(SdchOwnerPersistenceTest
, Persistent_BadVersion
) {
788 scoped_ptr
<base::DictionaryValue
> sdch_dict(new base::DictionaryValue());
789 sdch_dict
->SetInteger("version", 2);
790 pref_store_
->SetValue("SDCH", sdch_dict
.Pass(),
791 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS
);
794 EXPECT_EQ(0, owner_
->GetDictionaryCountForTesting());
797 // Test a persistence store with an empty dictionaries map.
798 TEST_F(SdchOwnerPersistenceTest
, Persistent_EmptyDictList
) {
799 scoped_ptr
<base::DictionaryValue
> sdch_dict(new base::DictionaryValue());
800 scoped_ptr
<base::DictionaryValue
> dicts(new base::DictionaryValue());
801 sdch_dict
->SetInteger("version", 1);
802 sdch_dict
->Set("dictionaries", dicts
.Pass());
803 pref_store_
->SetValue("SDCH", sdch_dict
.Pass(),
804 WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS
);
807 EXPECT_EQ(0, owner_
->GetDictionaryCountForTesting());
810 TEST_F(SdchOwnerPersistenceTest
, OneDict
) {
811 const GURL
url("http://www.example.com/dict");
813 EXPECT_EQ(0, owner_
->GetDictionaryCountForTesting());
814 InsertDictionaryForURL(url
, "0");
815 EXPECT_EQ(1, owner_
->GetDictionaryCountForTesting());
818 EXPECT_EQ(0, owner_
->GetDictionaryCountForTesting());
819 EXPECT_TRUE(CompleteLoadFromURL(url
, "0", true));
820 EXPECT_EQ(1, owner_
->GetDictionaryCountForTesting());
823 TEST_F(SdchOwnerPersistenceTest
, TwoDicts
) {
824 const GURL
url0("http://www.example.com/dict0");
825 const GURL
url1("http://www.example.com/dict1");
827 InsertDictionaryForURL(url0
, "0");
828 InsertDictionaryForURL(url1
, "1");
831 EXPECT_TRUE(CompleteLoadFromURL(url0
, "0", true));
832 EXPECT_TRUE(CompleteLoadFromURL(url1
, "1", true));
833 EXPECT_EQ(2, owner_
->GetDictionaryCountForTesting());
834 EXPECT_TRUE(owner_
->HasDictionaryFromURLForTesting(url0
));
835 EXPECT_TRUE(owner_
->HasDictionaryFromURLForTesting(url1
));
838 TEST_F(SdchOwnerPersistenceTest
, OneGoodDictOneBadDict
) {
839 const GURL
url0("http://www.example.com/dict0");
840 const GURL
url1("http://www.example.com/dict1");
842 InsertDictionaryForURL(url0
, "0");
843 InsertDictionaryForURL(url1
, "1");
845 // Mutate the pref store a bit now. Clear the owner first, to ensure that the
846 // SdchOwner doesn't observe these changes and object. The manual dictionary
847 // manipulation is a bit icky.
849 base::DictionaryValue
* dict
= nullptr;
850 ASSERT_TRUE(GetDictionaryForURL(pref_store_
.get(), url1
, nullptr, &dict
));
851 dict
->Remove("use_count", nullptr);
854 EXPECT_TRUE(CompleteLoadFromURL(url0
, "0", true));
855 EXPECT_FALSE(CompleteLoadFromURL(url1
, "1", true));
856 EXPECT_EQ(1, owner_
->GetDictionaryCountForTesting());
857 EXPECT_TRUE(owner_
->HasDictionaryFromURLForTesting(url0
));
858 EXPECT_FALSE(owner_
->HasDictionaryFromURLForTesting(url1
));
861 TEST_F(SdchOwnerPersistenceTest
, UsingDictionaryUpdatesUseCount
) {
862 const GURL
url("http://www.example.com/dict");
864 InsertDictionaryForURL(url
, "0");
870 base::DictionaryValue
* dict
= nullptr;
871 ASSERT_TRUE(GetDictionaryForURL(pref_store_
.get(), url
, &hash
, &dict
));
872 ASSERT_TRUE(dict
->GetInteger("use_count", &old_count
));
876 ASSERT_TRUE(CompleteLoadFromURL(url
, "0", true));
877 owner_
->OnDictionaryUsed(hash
);
882 base::DictionaryValue
* dict
= nullptr;
883 ASSERT_TRUE(GetDictionaryForURL(pref_store_
.get(), url
, nullptr, &dict
));
884 ASSERT_TRUE(dict
->GetInteger("use_count", &new_count
));
887 EXPECT_EQ(old_count
+ 1, new_count
);
890 TEST_F(SdchOwnerPersistenceTest
, LoadingDictionaryMerges
) {
891 const GURL
url0("http://www.example.com/dict0");
892 const GURL
url1("http://www.example.com/dict1");
895 InsertDictionaryForURL(url1
, "1");
898 InsertDictionaryForURL(url0
, "0");
899 EXPECT_EQ(1, owner_
->GetDictionaryCountForTesting());
900 owner_
->EnablePersistentStorage(pref_store_
.get());
901 ASSERT_TRUE(CompleteLoadFromURL(url1
, "1", true));
902 EXPECT_EQ(2, owner_
->GetDictionaryCountForTesting());
905 TEST_F(SdchOwnerPersistenceTest
, PersistenceMetrics
) {
906 const GURL
url0("http://www.example.com/dict0");
907 const GURL
url1("http://www.example.com/dict1");
910 InsertDictionaryForURL(url0
, "0");
911 InsertDictionaryForURL(url1
, "1");
915 base::HistogramTester tester
;
917 EXPECT_TRUE(CompleteLoadFromURL(url0
, "0", true));
918 EXPECT_TRUE(CompleteLoadFromURL(url1
, "1", false));
920 tester
.ExpectTotalCount("Sdch3.NetworkBytesSpent", 1);
921 tester
.ExpectUniqueSample("Sdch3.NetworkBytesSpent",
922 CreateDictionary(url1
, "1").size(), 1);