| // 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 "base/location.h" |
| #include "base/memory/memory_pressure_listener.h" |
| #include "base/prefs/testing_pref_store.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/histogram_tester.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/thread_task_runner_handle.h" |
| #include "base/values.h" |
| #include "net/base/sdch_manager.h" |
| #include "net/log/net_log.h" |
| #include "net/sdch/sdch_owner.h" |
| #include "net/url_request/url_request.h" |
| #include "net/url_request/url_request_context.h" |
| #include "net/url_request/url_request_error_job.h" |
| #include "net/url_request/url_request_job.h" |
| #include "net/url_request/url_request_job_factory.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace { |
| |
| bool GetDictionaryForURL(TestingPrefStore* store, |
| const GURL& url, |
| std::string* hash, |
| base::DictionaryValue** dict) { |
| base::Value* sdch_val = nullptr; |
| base::DictionaryValue* sdch_dict = nullptr; |
| if (!store->GetMutableValue("SDCH", &sdch_val)) |
| return false; |
| if (!sdch_val->GetAsDictionary(&sdch_dict)) |
| return false; |
| |
| base::DictionaryValue* dicts_dict = nullptr; |
| if (!sdch_dict->GetDictionary("dictionaries", &dicts_dict)) |
| return false; |
| |
| base::DictionaryValue::Iterator it(*dicts_dict); |
| while (!it.IsAtEnd()) { |
| const base::DictionaryValue* d = nullptr; |
| if (!it.value().GetAsDictionary(&d)) |
| continue; |
| std::string dict_url; |
| if (d->GetString("url", &dict_url) && dict_url == url.spec()) { |
| if (hash) |
| *hash = it.key(); |
| if (dict) |
| dicts_dict->GetDictionary(it.key(), dict); |
| return true; |
| } |
| it.Advance(); |
| } |
| |
| return false; |
| } |
| |
| } // namespace |
| |
| namespace net { |
| |
| static const char generic_url[] = "http://www.example.com"; |
| static const char generic_domain[] = "www.example.com"; |
| |
| static std::string NewSdchDictionary(size_t dictionary_size) { |
| std::string dictionary; |
| dictionary.append("Domain: "); |
| dictionary.append(generic_domain); |
| dictionary.append("\n"); |
| dictionary.append("\n"); |
| |
| size_t original_dictionary_size = dictionary.size(); |
| dictionary.resize(dictionary_size); |
| for (size_t i = original_dictionary_size; i < dictionary_size; ++i) |
| dictionary[i] = static_cast<char>((i % 127) + 1); |
| |
| return dictionary; |
| } |
| |
| int outstanding_url_request_error_counting_jobs = 0; |
| base::Closure* empty_url_request_jobs_callback = 0; |
| |
| // Variation of URLRequestErrorJob to count number of outstanding |
| // instances and notify when that goes to zero. |
| class URLRequestErrorCountingJob : public URLRequestJob { |
| public: |
| URLRequestErrorCountingJob(URLRequest* request, |
| NetworkDelegate* network_delegate, |
| int error) |
| : URLRequestJob(request, network_delegate), |
| error_(error), |
| weak_factory_(this) { |
| ++outstanding_url_request_error_counting_jobs; |
| } |
| |
| void Start() override { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&URLRequestErrorCountingJob::StartAsync, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| private: |
| ~URLRequestErrorCountingJob() override { |
| --outstanding_url_request_error_counting_jobs; |
| if (0 == outstanding_url_request_error_counting_jobs && |
| empty_url_request_jobs_callback) { |
| empty_url_request_jobs_callback->Run(); |
| } |
| } |
| |
| void StartAsync() { |
| NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, error_)); |
| } |
| |
| int error_; |
| |
| base::WeakPtrFactory<URLRequestErrorCountingJob> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(URLRequestErrorCountingJob); |
| }; |
| |
| static int error_jobs_created = 0; |
| |
| class MockURLRequestJobFactory : public URLRequestJobFactory { |
| public: |
| MockURLRequestJobFactory() {} |
| |
| ~MockURLRequestJobFactory() override {} |
| |
| URLRequestJob* MaybeCreateJobWithProtocolHandler( |
| const std::string& scheme, |
| URLRequest* request, |
| NetworkDelegate* network_delegate) const override { |
| ++error_jobs_created; |
| return new URLRequestErrorCountingJob(request, network_delegate, |
| ERR_INTERNET_DISCONNECTED); |
| } |
| |
| URLRequestJob* MaybeInterceptRedirect(URLRequest* request, |
| NetworkDelegate* network_delegate, |
| const GURL& location) const override { |
| return nullptr; |
| } |
| |
| URLRequestJob* MaybeInterceptResponse( |
| URLRequest* request, |
| NetworkDelegate* network_delegate) const override { |
| return nullptr; |
| } |
| |
| bool IsHandledProtocol(const std::string& scheme) const override { |
| return scheme == "http"; |
| }; |
| |
| bool IsHandledURL(const GURL& url) const override { |
| return url.SchemeIs("http"); |
| } |
| |
| bool IsSafeRedirectTarget(const GURL& location) const override { |
| return false; |
| } |
| }; |
| |
| class MockSdchDictionaryFetcher : public SdchDictionaryFetcher { |
| public: |
| MockSdchDictionaryFetcher() : SdchDictionaryFetcher(&test_context_) {} |
| ~MockSdchDictionaryFetcher() {} |
| |
| struct PendingRequest { |
| PendingRequest(const GURL& url, |
| const OnDictionaryFetchedCallback& callback) |
| : url_(url), callback_(callback) {} |
| GURL url_; |
| OnDictionaryFetchedCallback callback_; |
| }; |
| |
| virtual bool Schedule(const GURL& dictionary_url, |
| const OnDictionaryFetchedCallback& callback) { |
| if (!HasPendingRequest(dictionary_url)) { |
| requests_.push_back(PendingRequest(dictionary_url, callback)); |
| return true; |
| } |
| return false; |
| } |
| |
| virtual bool ScheduleReload(const GURL& dictionary_url, |
| const OnDictionaryFetchedCallback& callback) { |
| if (!HasPendingRequest(dictionary_url)) { |
| requests_.push_back(PendingRequest(dictionary_url, callback)); |
| return true; |
| } |
| return false; |
| } |
| |
| virtual void Cancel() { |
| requests_.clear(); |
| } |
| |
| bool HasPendingRequest(const GURL& dictionary_url) { |
| for (std::vector<PendingRequest>::iterator it = requests_.begin(); |
| it != requests_.end(); ++it) { |
| if (it->url_ == dictionary_url) |
| return true; |
| } |
| return false; |
| } |
| |
| bool CompletePendingRequest(const GURL& dictionary_url, |
| const std::string& dictionary_text, |
| const BoundNetLog& net_log, |
| bool was_from_cache) { |
| for (std::vector<PendingRequest>::iterator it = requests_.begin(); |
| it != requests_.end(); ++it) { |
| if (it->url_ == dictionary_url) { |
| it->callback_.Run(dictionary_text, dictionary_url, net_log, |
| was_from_cache); |
| requests_.erase(it); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private: |
| TestURLRequestContext test_context_; |
| std::vector<PendingRequest> requests_; |
| DISALLOW_COPY_AND_ASSIGN(MockSdchDictionaryFetcher); |
| }; |
| |
| // File testing infrastructure summary: |
| // * NewSdchDictionary(): Creates a dictionary of a specific size. |
| // * URLRequestErrorCountingJob: A URLRequestJob that returns an error |
| // and counts the number of outstanding (started but not finished) |
| // jobs, and calls a global callback when that number transitions to zero. |
| // * MockURLRequestJobFactory: Factory to create the above jobs. Tracks |
| // the number of jobs created. |
| // * SdchOwnerTest: Interfaces |
| // * Access manager, owner, and net log |
| // * Return the number of jobs created in a time interval |
| // * Return dictionary present in the manager |
| // * Notify SdchOwner of an incoming dictionary (& wait until jobs clear) |
| // * Attempt to add a dictionary and test for success. |
| // Test patterns: |
| // * Let the owner know about a Get-Dictionary header and test for |
| // appropriate jobs being created. |
| // * Let the owner know that a dictionary was successfully fetched |
| // and test for appropriate outcome. |
| // * Either of the above, having previously added dictionaries to create |
| // a particular initial state. |
| class SdchOwnerTest : public testing::Test { |
| public: |
| static const size_t kMaxSizeForTesting = 1000 * 50; |
| static const size_t kMinFetchSpaceForTesting = 500; |
| |
| SdchOwnerTest() |
| : last_jobs_created_(error_jobs_created), |
| dictionary_creation_index_(0), |
| pref_store_(new TestingPrefStore), |
| sdch_owner_(new SdchOwner(&sdch_manager_, &url_request_context_)) { |
| // Any jobs created on this context will immediately error, |
| // which leaves the test in control of signals to SdchOwner. |
| url_request_context_.set_job_factory(&job_factory_); |
| |
| // Reduce sizes to reduce time for string operations. |
| sdch_owner_->SetMaxTotalDictionarySize(kMaxSizeForTesting); |
| sdch_owner_->SetMinSpaceForDictionaryFetch(kMinFetchSpaceForTesting); |
| } |
| |
| SdchManager& sdch_manager() { return sdch_manager_; } |
| SdchOwner& sdch_owner() { return *(sdch_owner_.get()); } |
| BoundNetLog& bound_net_log() { return net_log_; } |
| TestingPrefStore& pref_store() { return *(pref_store_.get()); } |
| |
| int JobsRecentlyCreated() { |
| int result = error_jobs_created - last_jobs_created_; |
| last_jobs_created_ = error_jobs_created; |
| return result; |
| } |
| |
| bool DictionaryPresentInManager(const std::string& server_hash) { |
| // Presumes all tests use generic url. |
| SdchProblemCode tmp; |
| scoped_ptr<SdchManager::DictionarySet> set( |
| sdch_manager_.GetDictionarySetByHash(GURL(generic_url), server_hash, |
| &tmp)); |
| return !!set.get(); |
| } |
| |
| void WaitForNoJobs() { |
| if (outstanding_url_request_error_counting_jobs == 0) |
| return; |
| |
| base::RunLoop run_loop; |
| base::Closure quit_closure(run_loop.QuitClosure()); |
| empty_url_request_jobs_callback = &quit_closure; |
| run_loop.Run(); |
| empty_url_request_jobs_callback = NULL; |
| } |
| |
| void SignalGetDictionaryAndClearJobs(GURL request_url, GURL dictionary_url) { |
| sdch_owner().OnGetDictionary(request_url, dictionary_url); |
| WaitForNoJobs(); |
| } |
| |
| // Create a unique (by hash) dictionary of the given size, |
| // associate it with a unique URL, add it to the manager through |
| // SdchOwner::OnDictionaryFetched(), and return whether that |
| // addition was successful or not. |
| bool CreateAndAddDictionary(size_t size, |
| std::string* server_hash_p, |
| base::Time last_used_time) { |
| GURL dictionary_url( |
| base::StringPrintf("%s/d%d", generic_url, dictionary_creation_index_)); |
| std::string dictionary_text(NewSdchDictionary(size - 4)); |
| dictionary_text += base::StringPrintf("%04d", dictionary_creation_index_); |
| ++dictionary_creation_index_; |
| std::string client_hash; |
| std::string server_hash; |
| SdchManager::GenerateHash(dictionary_text, &client_hash, &server_hash); |
| |
| if (DictionaryPresentInManager(server_hash)) |
| return false; |
| sdch_owner().OnDictionaryFetched(last_used_time, 0, dictionary_text, |
| dictionary_url, net_log_, false); |
| if (server_hash_p) |
| *server_hash_p = server_hash; |
| return DictionaryPresentInManager(server_hash); |
| } |
| |
| void ResetOwner() { |
| sdch_owner_.reset(new SdchOwner(&sdch_manager_, &url_request_context_)); |
| } |
| |
| private: |
| int last_jobs_created_; |
| BoundNetLog net_log_; |
| int dictionary_creation_index_; |
| |
| // The dependencies of these objects (sdch_owner_ -> {sdch_manager_, |
| // url_request_context_}, url_request_context_->job_factory_) require |
| // this order for correct destruction semantics. |
| MockURLRequestJobFactory job_factory_; |
| URLRequestContext url_request_context_; |
| SdchManager sdch_manager_; |
| scoped_refptr<TestingPrefStore> pref_store_; |
| scoped_ptr<SdchOwner> sdch_owner_; |
| |
| DISALLOW_COPY_AND_ASSIGN(SdchOwnerTest); |
| }; |
| |
| // Does OnGetDictionary result in a fetch when there's enough space, and not |
| // when there's not? |
| TEST_F(SdchOwnerTest, OnGetDictionary_Fetching) { |
| GURL request_url(std::string(generic_url) + "/r1"); |
| |
| // Fetch generated when empty. |
| GURL dict_url1(std::string(generic_url) + "/d1"); |
| EXPECT_EQ(0, JobsRecentlyCreated()); |
| SignalGetDictionaryAndClearJobs(request_url, dict_url1); |
| EXPECT_EQ(1, JobsRecentlyCreated()); |
| |
| // Fetch generated when half full. |
| GURL dict_url2(std::string(generic_url) + "/d2"); |
| std::string dictionary1(NewSdchDictionary(kMaxSizeForTesting / 2)); |
| sdch_owner().OnDictionaryFetched(base::Time::Now(), 1, dictionary1, |
| dict_url1, bound_net_log(), false); |
| EXPECT_EQ(0, JobsRecentlyCreated()); |
| SignalGetDictionaryAndClearJobs(request_url, dict_url2); |
| EXPECT_EQ(1, JobsRecentlyCreated()); |
| |
| // Fetch not generated when close to completely full. |
| GURL dict_url3(std::string(generic_url) + "/d3"); |
| std::string dictionary2(NewSdchDictionary( |
| (kMaxSizeForTesting / 2 - kMinFetchSpaceForTesting / 2))); |
| sdch_owner().OnDictionaryFetched(base::Time::Now(), 1, dictionary2, |
| dict_url2, bound_net_log(), false); |
| EXPECT_EQ(0, JobsRecentlyCreated()); |
| SignalGetDictionaryAndClearJobs(request_url, dict_url3); |
| EXPECT_EQ(0, JobsRecentlyCreated()); |
| } |
| |
| // Make sure attempts to add dictionaries do what they should. |
| TEST_F(SdchOwnerTest, OnDictionaryFetched_Fetching) { |
| GURL request_url(std::string(generic_url) + "/r1"); |
| std::string client_hash; |
| std::string server_hash; |
| |
| // In the past, but still fresh for an unused dictionary. |
| base::Time dictionary_last_used_time(base::Time::Now() - |
| base::TimeDelta::FromMinutes(30)); |
| |
| // Add successful when empty. |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, nullptr, |
| dictionary_last_used_time)); |
| EXPECT_EQ(0, JobsRecentlyCreated()); |
| |
| // Add successful when half full. |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, nullptr, |
| dictionary_last_used_time)); |
| EXPECT_EQ(0, JobsRecentlyCreated()); |
| |
| // Add unsuccessful when full. |
| EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting / 2, nullptr, |
| dictionary_last_used_time)); |
| EXPECT_EQ(0, JobsRecentlyCreated()); |
| } |
| |
| // Confirm auto-eviction happens if space is needed. |
| TEST_F(SdchOwnerTest, ConfirmAutoEviction) { |
| base::Time start_time = base::Time::Now(); |
| std::string server_hash_d1; |
| std::string server_hash_d2; |
| std::string server_hash_d3; |
| |
| base::SimpleTestClock* test_clock = new base::SimpleTestClock(); |
| sdch_owner().SetClockForTesting(make_scoped_ptr(test_clock)); |
| test_clock->SetNow(base::Time::Now()); |
| |
| // Add two dictionaries, one recent, one more than a day in the past. |
| base::Time fresh(base::Time::Now() - base::TimeDelta::FromHours(23)); |
| base::Time stale(base::Time::Now() - base::TimeDelta::FromHours(25)); |
| |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d1, fresh)); |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d2, stale)); |
| |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); |
| |
| base::HistogramTester tester; |
| const base::TimeDelta synthetic_delta = base::TimeDelta::FromSeconds(5); |
| |
| test_clock->Advance(synthetic_delta); |
| |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d3, fresh)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); |
| |
| base::TimeDelta expected_proc_lifetime = synthetic_delta * 3 + |
| base::Time::Now() - start_time; |
| size_t expected_value_base = ((kMaxSizeForTesting / 2) * |
| synthetic_delta.InMilliseconds()) / |
| expected_proc_lifetime.InMilliseconds(); |
| |
| const char *kHistogram = "Sdch3.TimeWeightedMemoryUse"; |
| tester.ExpectTotalCount(kHistogram, 0); |
| |
| // Dictionary insertions and deletions: |
| // T = 0: insert d1 and d2 |
| // T = 5: insert d3, which evicts d2 |
| // T = 15: destroy SdchOwner, which evicts d1 and d3 |
| // Therefore, d2's lifetime is synthetic_delta, d1's is synthetic_delta * 3, |
| // and d3's is synthetic_delta * 2. The expected_value_base variable is the |
| // base factor for d2's memory consumption, of which d1's and d3's are |
| // multiples. |
| test_clock->Advance(synthetic_delta * 2); |
| ResetOwner(); |
| |
| tester.ExpectTotalCount(kHistogram, 3); |
| tester.ExpectBucketCount(kHistogram, expected_value_base, 1); |
| tester.ExpectBucketCount(kHistogram, expected_value_base * 2, 1); |
| tester.ExpectBucketCount(kHistogram, expected_value_base * 3, 1); |
| } |
| |
| // Confirm auto-eviction happens if space is needed, with a more complicated |
| // situation |
| TEST_F(SdchOwnerTest, ConfirmAutoEviction_2) { |
| std::string server_hash_d1; |
| std::string server_hash_d2; |
| std::string server_hash_d3; |
| |
| // Add dictionaries, one recent, two more than a day in the past that |
| // between them add up to the space needed. |
| base::Time fresh(base::Time::Now() - base::TimeDelta::FromHours(23)); |
| base::Time stale(base::Time::Now() - base::TimeDelta::FromHours(25)); |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d1, fresh)); |
| |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2, stale)); |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3, stale)); |
| |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); |
| |
| std::string server_hash_d4; |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4, fresh)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d3)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4)); |
| } |
| |
| // Confirm if only one dictionary needs to be evicted it's the oldest. |
| TEST_F(SdchOwnerTest, ConfirmAutoEviction_Oldest) { |
| std::string server_hash_d1; |
| std::string server_hash_d2; |
| std::string server_hash_d3; |
| |
| // Add dictionaries, one recent, one two days in the past, and one |
| // four days in the past. |
| base::Time fresh(base::Time::Now() - base::TimeDelta::FromHours(23)); |
| base::Time stale_newer(base::Time::Now() - base::TimeDelta::FromHours(47)); |
| base::Time stale_older(base::Time::Now() - base::TimeDelta::FromHours(71)); |
| |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d1, fresh)); |
| |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2, |
| stale_newer)); |
| |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3, |
| stale_older)); |
| |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); |
| |
| // The addition of a new dictionary should succeed, evicting only the |
| // oldest one. |
| |
| std::string server_hash_d4; |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4, fresh)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d3)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4)); |
| } |
| |
| // Confirm using a dictionary changes eviction behavior properly. |
| TEST_F(SdchOwnerTest, UseChangesEviction) { |
| std::string server_hash_d1; |
| std::string server_hash_d2; |
| std::string server_hash_d3; |
| |
| // Add dictionaries, one recent, one two days in the past, and one |
| // four days in the past. |
| base::Time fresh(base::Time::Now() - base::TimeDelta::FromHours(23)); |
| base::Time stale_newer(base::Time::Now() - base::TimeDelta::FromHours(47)); |
| base::Time stale_older(base::Time::Now() - base::TimeDelta::FromHours(71)); |
| |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d1, fresh)); |
| |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2, |
| stale_newer)); |
| |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3, |
| stale_older)); |
| |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); |
| |
| // Use the oldest dictionary. |
| sdch_owner().OnDictionaryUsed(server_hash_d3); |
| |
| // The addition of a new dictionary should succeed, evicting only the |
| // newer stale one. |
| std::string server_hash_d4; |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4, fresh)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d4)); |
| } |
| |
| // Confirm using a dictionary can prevent the addition of a new dictionary. |
| TEST_F(SdchOwnerTest, UsePreventsAddition) { |
| std::string server_hash_d1; |
| std::string server_hash_d2; |
| std::string server_hash_d3; |
| |
| // Add dictionaries, one recent, one two days in the past, and one |
| // four days in the past. |
| base::Time fresh(base::Time::Now() - base::TimeDelta::FromMinutes(30)); |
| base::Time stale_newer(base::Time::Now() - base::TimeDelta::FromHours(47)); |
| base::Time stale_older(base::Time::Now() - base::TimeDelta::FromHours(71)); |
| |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d1, fresh)); |
| |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2, |
| stale_newer)); |
| |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3, |
| stale_older)); |
| |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); |
| |
| // Use the older dictionaries. |
| sdch_owner().OnDictionaryUsed(server_hash_d2); |
| sdch_owner().OnDictionaryUsed(server_hash_d3); |
| |
| // The addition of a new dictionary should fail, not evicting anything. |
| std::string server_hash_d4; |
| EXPECT_FALSE( |
| CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4, fresh)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2)); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d4)); |
| } |
| |
| // Confirm clear gets all the space back. |
| TEST_F(SdchOwnerTest, ClearReturnsSpace) { |
| std::string server_hash_d1; |
| std::string server_hash_d2; |
| |
| // Take up all the space. |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d1, |
| base::Time::Now())); |
| // Addition should fail. |
| EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d2, |
| base::Time::Now())); |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); |
| sdch_manager().ClearData(); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); |
| |
| // Addition should now succeed. |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting, nullptr, base::Time::Now())); |
| } |
| |
| // Confirm memory pressure gets all the space back. |
| TEST_F(SdchOwnerTest, MemoryPressureReturnsSpace) { |
| std::string server_hash_d1; |
| std::string server_hash_d2; |
| |
| // Take up all the space. |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d1, |
| base::Time::Now())); |
| |
| // Addition should fail. |
| EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d2, |
| base::Time::Now())); |
| |
| EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); |
| |
| base::MemoryPressureListener::NotifyMemoryPressure( |
| base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE); |
| // The notification may have (implementation note: does :-}) use a PostTask, |
| // so we drain the local message queue. This should be safe (i.e. not have |
| // an inifinite number of messages) in a unit test. |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d1)); |
| EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2)); |
| |
| // Addition should now succeed. |
| EXPECT_TRUE( |
| CreateAndAddDictionary(kMaxSizeForTesting, nullptr, base::Time::Now())); |
| } |
| |
| // Confirm that use of a pinned dictionary after its removal works properly. |
| TEST_F(SdchOwnerTest, PinRemoveUse) { |
| pref_store().SetInitializationCompleted(); |
| sdch_owner().EnablePersistentStorage(&pref_store()); |
| |
| std::string server_hash_d1; |
| EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d1, |
| base::Time::Now())); |
| |
| scoped_ptr<SdchManager::DictionarySet> return_set( |
| sdch_manager().GetDictionarySet( |
| GURL(std::string(generic_url) + "/x.html"))); |
| ASSERT_TRUE(return_set.get()); |
| EXPECT_TRUE(return_set->GetDictionaryText(server_hash_d1)); |
| |
| const base::Value* result = nullptr; |
| const base::DictionaryValue* dict_result = nullptr; |
| ASSERT_TRUE(pref_store().GetValue("SDCH", &result)); |
| ASSERT_TRUE(result->GetAsDictionary(&dict_result)); |
| EXPECT_TRUE(dict_result->Get("dictionaries", &result)); |
| EXPECT_TRUE(dict_result->Get("dictionaries." + server_hash_d1, &result)); |
| |
| sdch_manager().ClearData(); |
| |
| ASSERT_TRUE(pref_store().GetValue("SDCH", &result)); |
| ASSERT_TRUE(result->GetAsDictionary(&dict_result)); |
| EXPECT_TRUE(dict_result->Get("dictionaries", &result)); |
| EXPECT_FALSE(dict_result->Get("dictionaries." + server_hash_d1, &result)); |
| |
| scoped_ptr<SdchManager::DictionarySet> return_set2( |
| sdch_manager().GetDictionarySet( |
| GURL(std::string(generic_url) + "/x.html"))); |
| EXPECT_FALSE(return_set2.get()); |
| |
| sdch_manager().OnDictionaryUsed(server_hash_d1); |
| |
| ASSERT_TRUE(pref_store().GetValue("SDCH", &result)); |
| ASSERT_TRUE(result->GetAsDictionary(&dict_result)); |
| EXPECT_TRUE(dict_result->Get("dictionaries", &result)); |
| EXPECT_FALSE(dict_result->Get("dictionaries." + server_hash_d1, &result)); |
| } |
| |
| class SdchOwnerPersistenceTest : public ::testing::Test { |
| public: |
| SdchOwnerPersistenceTest() : pref_store_(new TestingPrefStore()) { |
| pref_store_->SetInitializationCompleted(); |
| } |
| virtual ~SdchOwnerPersistenceTest() {} |
| |
| void ClearOwner() { |
| owner_.reset(NULL); |
| } |
| |
| void ResetOwner(bool delay) { |
| // This has to be done first, since SdchOwner may be observing SdchManager, |
| // and SdchManager can't be destroyed with a live observer. |
| owner_.reset(NULL); |
| manager_.reset(new SdchManager()); |
| fetcher_ = new MockSdchDictionaryFetcher(); |
| owner_.reset(new SdchOwner(manager_.get(), |
| &url_request_context_)); |
| owner_->SetMaxTotalDictionarySize(SdchOwnerTest::kMaxSizeForTesting); |
| owner_->SetMinSpaceForDictionaryFetch( |
| SdchOwnerTest::kMinFetchSpaceForTesting); |
| owner_->SetFetcherForTesting(make_scoped_ptr(fetcher_)); |
| if (!delay) |
| owner_->EnablePersistentStorage(pref_store_.get()); |
| } |
| |
| void InsertDictionaryForURL(const GURL& url, const std::string& nonce) { |
| owner_->OnDictionaryFetched(base::Time::Now(), 1, |
| CreateDictionary(url, nonce), |
| url, net_log_, false); |
| } |
| |
| bool CompleteLoadFromURL(const GURL& url, const std::string& nonce, |
| bool was_from_cache) { |
| return fetcher_->CompletePendingRequest(url, CreateDictionary(url, nonce), |
| net_log_, was_from_cache); |
| } |
| |
| std::string CreateDictionary(const GURL& url, const std::string& nonce) { |
| std::string dict; |
| dict.append("Domain: "); |
| dict.append(url.host()); |
| dict.append("\n\n"); |
| dict.append(url.spec()); |
| dict.append(nonce); |
| return dict; |
| } |
| |
| protected: |
| BoundNetLog net_log_; |
| scoped_refptr<TestingPrefStore> pref_store_; |
| scoped_ptr<SdchManager> manager_; |
| MockSdchDictionaryFetcher* fetcher_; |
| scoped_ptr<SdchOwner> owner_; |
| TestURLRequestContext url_request_context_; |
| }; |
| |
| // Test an empty persistence store. |
| TEST_F(SdchOwnerPersistenceTest, Empty) { |
| ResetOwner(false); |
| EXPECT_EQ(0, owner_->GetDictionaryCountForTesting()); |
| } |
| |
| // Test a persistence store with an empty dictionary. |
| TEST_F(SdchOwnerPersistenceTest, Persistent_EmptyDict) { |
| pref_store_->SetValue("SDCH", make_scoped_ptr(new base::DictionaryValue()), |
| WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); |
| ResetOwner(false); |
| EXPECT_EQ(0, owner_->GetDictionaryCountForTesting()); |
| } |
| |
| // Test a persistence store with a bad version number. |
| TEST_F(SdchOwnerPersistenceTest, Persistent_BadVersion) { |
| scoped_ptr<base::DictionaryValue> sdch_dict(new base::DictionaryValue()); |
| sdch_dict->SetInteger("version", 2); |
| pref_store_->SetValue("SDCH", sdch_dict.Pass(), |
| WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); |
| |
| ResetOwner(false); |
| EXPECT_EQ(0, owner_->GetDictionaryCountForTesting()); |
| } |
| |
| // Test a persistence store with an empty dictionaries map. |
| TEST_F(SdchOwnerPersistenceTest, Persistent_EmptyDictList) { |
| scoped_ptr<base::DictionaryValue> sdch_dict(new base::DictionaryValue()); |
| scoped_ptr<base::DictionaryValue> dicts(new base::DictionaryValue()); |
| sdch_dict->SetInteger("version", 1); |
| sdch_dict->Set("dictionaries", dicts.Pass()); |
| pref_store_->SetValue("SDCH", sdch_dict.Pass(), |
| WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS); |
| |
| ResetOwner(false); |
| EXPECT_EQ(0, owner_->GetDictionaryCountForTesting()); |
| } |
| |
| TEST_F(SdchOwnerPersistenceTest, OneDict) { |
| const GURL url("http://www.example.com/dict"); |
| ResetOwner(false); |
| EXPECT_EQ(0, owner_->GetDictionaryCountForTesting()); |
| InsertDictionaryForURL(url, "0"); |
| EXPECT_EQ(1, owner_->GetDictionaryCountForTesting()); |
| |
| ResetOwner(false); |
| EXPECT_EQ(0, owner_->GetDictionaryCountForTesting()); |
| EXPECT_TRUE(CompleteLoadFromURL(url, "0", true)); |
| EXPECT_EQ(1, owner_->GetDictionaryCountForTesting()); |
| } |
| |
| TEST_F(SdchOwnerPersistenceTest, TwoDicts) { |
| const GURL url0("http://www.example.com/dict0"); |
| const GURL url1("http://www.example.com/dict1"); |
| ResetOwner(false); |
| InsertDictionaryForURL(url0, "0"); |
| InsertDictionaryForURL(url1, "1"); |
| |
| ResetOwner(false); |
| EXPECT_TRUE(CompleteLoadFromURL(url0, "0", true)); |
| EXPECT_TRUE(CompleteLoadFromURL(url1, "1", true)); |
| EXPECT_EQ(2, owner_->GetDictionaryCountForTesting()); |
| EXPECT_TRUE(owner_->HasDictionaryFromURLForTesting(url0)); |
| EXPECT_TRUE(owner_->HasDictionaryFromURLForTesting(url1)); |
| } |
| |
| TEST_F(SdchOwnerPersistenceTest, OneGoodDictOneBadDict) { |
| const GURL url0("http://www.example.com/dict0"); |
| const GURL url1("http://www.example.com/dict1"); |
| ResetOwner(false); |
| InsertDictionaryForURL(url0, "0"); |
| InsertDictionaryForURL(url1, "1"); |
| |
| // Mutate the pref store a bit now. Clear the owner first, to ensure that the |
| // SdchOwner doesn't observe these changes and object. The manual dictionary |
| // manipulation is a bit icky. |
| ClearOwner(); |
| base::DictionaryValue* dict = nullptr; |
| ASSERT_TRUE(GetDictionaryForURL(pref_store_.get(), url1, nullptr, &dict)); |
| dict->Remove("use_count", nullptr); |
| |
| ResetOwner(false); |
| EXPECT_TRUE(CompleteLoadFromURL(url0, "0", true)); |
| EXPECT_FALSE(CompleteLoadFromURL(url1, "1", true)); |
| EXPECT_EQ(1, owner_->GetDictionaryCountForTesting()); |
| EXPECT_TRUE(owner_->HasDictionaryFromURLForTesting(url0)); |
| EXPECT_FALSE(owner_->HasDictionaryFromURLForTesting(url1)); |
| } |
| |
| TEST_F(SdchOwnerPersistenceTest, UsingDictionaryUpdatesUseCount) { |
| const GURL url("http://www.example.com/dict"); |
| ResetOwner(false); |
| InsertDictionaryForURL(url, "0"); |
| |
| std::string hash; |
| int old_count; |
| { |
| ClearOwner(); |
| base::DictionaryValue* dict = nullptr; |
| ASSERT_TRUE(GetDictionaryForURL(pref_store_.get(), url, &hash, &dict)); |
| ASSERT_TRUE(dict->GetInteger("use_count", &old_count)); |
| } |
| |
| ResetOwner(false); |
| ASSERT_TRUE(CompleteLoadFromURL(url, "0", true)); |
| owner_->OnDictionaryUsed(hash); |
| |
| int new_count; |
| { |
| ClearOwner(); |
| base::DictionaryValue* dict = nullptr; |
| ASSERT_TRUE(GetDictionaryForURL(pref_store_.get(), url, nullptr, &dict)); |
| ASSERT_TRUE(dict->GetInteger("use_count", &new_count)); |
| } |
| |
| EXPECT_EQ(old_count + 1, new_count); |
| } |
| |
| TEST_F(SdchOwnerPersistenceTest, LoadingDictionaryMerges) { |
| const GURL url0("http://www.example.com/dict0"); |
| const GURL url1("http://www.example.com/dict1"); |
| |
| ResetOwner(false); |
| InsertDictionaryForURL(url1, "1"); |
| |
| ResetOwner(true); |
| InsertDictionaryForURL(url0, "0"); |
| EXPECT_EQ(1, owner_->GetDictionaryCountForTesting()); |
| owner_->EnablePersistentStorage(pref_store_.get()); |
| ASSERT_TRUE(CompleteLoadFromURL(url1, "1", true)); |
| EXPECT_EQ(2, owner_->GetDictionaryCountForTesting()); |
| } |
| |
| TEST_F(SdchOwnerPersistenceTest, PersistenceMetrics) { |
| const GURL url0("http://www.example.com/dict0"); |
| const GURL url1("http://www.example.com/dict1"); |
| ResetOwner(false); |
| |
| InsertDictionaryForURL(url0, "0"); |
| InsertDictionaryForURL(url1, "1"); |
| |
| ResetOwner(false); |
| |
| base::HistogramTester tester; |
| |
| EXPECT_TRUE(CompleteLoadFromURL(url0, "0", true)); |
| EXPECT_TRUE(CompleteLoadFromURL(url1, "1", false)); |
| |
| tester.ExpectTotalCount("Sdch3.NetworkBytesSpent", 1); |
| tester.ExpectUniqueSample("Sdch3.NetworkBytesSpent", |
| CreateDictionary(url1, "1").size(), 1); |
| } |
| |
| } // namespace net |