blob: e89533a361c4a45477c6affcf16a87ea2820031b [file] [log] [blame]
// 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/memory/memory_pressure_listener.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/simple_test_clock.h"
#include "net/base/net_log.h"
#include "net/base/sdch_manager.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 "testing/gtest/include/gtest/gtest.h"
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::MessageLoop::current()->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 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),
sdch_owner_(&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_; }
BoundNetLog& bound_net_log() { return net_log_; }
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 SignalGetDictionaryAndClearJobs(GURL request_url, GURL dictionary_url) {
sdch_owner().OnGetDictionary(&sdch_manager_, request_url, dictionary_url);
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;
}
// 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) {
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(dictionary_text, dictionary_url, net_log_);
if (server_hash_p)
*server_hash_p = server_hash;
return DictionaryPresentInManager(server_hash);
}
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_;
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(dictionary1, dict_url1, bound_net_log());
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(dictionary2, dict_url2, bound_net_log());
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;
// Add successful when empty.
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, nullptr));
EXPECT_EQ(0, JobsRecentlyCreated());
// Add successful when half full.
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, nullptr));
EXPECT_EQ(0, JobsRecentlyCreated());
// Add unsuccessful when full.
EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting / 2, nullptr));
EXPECT_EQ(0, JobsRecentlyCreated());
}
// Confirm auto-eviction happens if space is needed.
TEST_F(SdchOwnerTest, ConfirmAutoEviction) {
std::string server_hash_d1;
std::string server_hash_d2;
std::string server_hash_d3;
// Add two dictionaries, one recent, one more than a day in the past.
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d1));
scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock);
clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
sdch_owner().SetClockForTesting(clock.Pass());
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d2));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2));
// The addition of a new dictionary should succeed, evicting the old one.
clock.reset(new base::SimpleTestClock);
clock->SetNow(base::Time::Now());
sdch_owner().SetClockForTesting(clock.Pass());
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d3));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1));
EXPECT_FALSE(DictionaryPresentInManager(server_hash_d2));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3));
}
// 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.
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d1));
scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock);
clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
sdch_owner().SetClockForTesting(clock.Pass());
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2));
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3));
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 the old one.
clock.reset(new base::SimpleTestClock);
clock->SetNow(base::Time::Now());
sdch_owner().SetClockForTesting(clock.Pass());
std::string server_hash_d4;
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4));
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.
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d1));
scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock);
clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
sdch_owner().SetClockForTesting(clock.Pass());
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2));
clock.reset(new base::SimpleTestClock);
clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(4));
sdch_owner().SetClockForTesting(clock.Pass());
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3));
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.
clock.reset(new base::SimpleTestClock);
clock->SetNow(base::Time::Now());
sdch_owner().SetClockForTesting(clock.Pass());
std::string server_hash_d4;
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 2, &server_hash_d4));
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.
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d1));
scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock);
clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
sdch_owner().SetClockForTesting(clock.Pass());
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2));
clock.reset(new base::SimpleTestClock);
clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(4));
sdch_owner().SetClockForTesting(clock.Pass());
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3));
clock.reset(new base::SimpleTestClock);
clock->SetNow(base::Time::Now());
sdch_owner().SetClockForTesting(clock.Pass());
// Use the oldest dictionary.
sdch_owner().OnDictionaryUsed(&sdch_manager(), 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));
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.
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d1));
scoped_ptr<base::SimpleTestClock> clock(new base::SimpleTestClock);
clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(2));
sdch_owner().SetClockForTesting(clock.Pass());
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d2));
clock.reset(new base::SimpleTestClock);
clock->SetNow(base::Time::Now() - base::TimeDelta::FromDays(4));
sdch_owner().SetClockForTesting(clock.Pass());
EXPECT_TRUE(CreateAndAddDictionary(kMaxSizeForTesting / 4, &server_hash_d3));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d1));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d2));
EXPECT_TRUE(DictionaryPresentInManager(server_hash_d3));
clock.reset(new base::SimpleTestClock);
clock->SetNow(base::Time::Now());
sdch_owner().SetClockForTesting(clock.Pass());
// Use the older dictionaries.
sdch_owner().OnDictionaryUsed(&sdch_manager(), server_hash_d2);
sdch_owner().OnDictionaryUsed(&sdch_manager(), 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));
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));
// Addition should fail.
EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d2));
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));
}
// 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));
// Addition should fail.
EXPECT_FALSE(CreateAndAddDictionary(kMaxSizeForTesting, &server_hash_d2));
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));
}
} // namespace net