// Copyright (c) 2012 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 "net/http/mock_http_cache.h"
#include "base/bind.h"
#include "base/message_loop/message_loop.h"
#include "net/base/completion_callback.h"
#include "net/base/net_errors.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
// We can override the test mode for a given operation by setting this global
// variable.
int g_test_mode = 0;
int GetTestModeForEntry(const std::string& key) {
// 'key' is prefixed with an identifier if it corresponds to a cached POST.
// Skip past that to locate the actual URL.
//
// TODO(darin): It breaks the abstraction a bit that we assume 'key' is an
// URL corresponding to a registered MockTransaction. It would be good to
// have another way to access the test_mode.
GURL url;
if (isdigit(key[0])) {
size_t slash = key.find('/');
DCHECK(slash != std::string::npos);
url = GURL(key.substr(slash + 1));
} else {
url = GURL(key);
}
const MockTransaction* t = FindMockTransaction(url);
DCHECK(t);
return t->test_mode;
}
void CallbackForwader(const net::CompletionCallback& callback, int result) {
callback.Run(result);
}
} // namespace
//-----------------------------------------------------------------------------
struct MockDiskEntry::CallbackInfo {
scoped_refptr<MockDiskEntry> entry;
net::CompletionCallback callback;
int result;
};
MockDiskEntry::MockDiskEntry(const std::string& key)
: key_(key), doomed_(false), sparse_(false),
fail_requests_(false), fail_sparse_requests_(false), busy_(false),
delayed_(false) {
test_mode_ = GetTestModeForEntry(key);
}
void MockDiskEntry::Doom() {
doomed_ = true;
}
void MockDiskEntry::Close() {
Release();
}
std::string MockDiskEntry::GetKey() const {
return key_;
}
base::Time MockDiskEntry::GetLastUsed() const {
return base::Time::FromInternalValue(0);
}
base::Time MockDiskEntry::GetLastModified() const {
return base::Time::FromInternalValue(0);
}
int32 MockDiskEntry::GetDataSize(int index) const {
DCHECK(index >= 0 && index < kNumCacheEntryDataIndices);
return static_cast<int32>(data_[index].size());
}
int MockDiskEntry::ReadData(
int index, int offset, net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback) {
DCHECK(index >= 0 && index < kNumCacheEntryDataIndices);
DCHECK(!callback.is_null());
if (fail_requests_)
return net::ERR_CACHE_READ_FAILURE;
if (offset < 0 || offset > static_cast<int>(data_[index].size()))
return net::ERR_FAILED;
if (static_cast<size_t>(offset) == data_[index].size())
return 0;
int num = std::min(buf_len, static_cast<int>(data_[index].size()) - offset);
memcpy(buf->data(), &data_[index][offset], num);
if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_READ)
return num;
CallbackLater(callback, num);
return net::ERR_IO_PENDING;
}
int MockDiskEntry::WriteData(
int index, int offset, net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback, bool truncate) {
DCHECK(index >= 0 && index < kNumCacheEntryDataIndices);
DCHECK(!callback.is_null());
DCHECK(truncate);
if (fail_requests_) {
CallbackLater(callback, net::ERR_CACHE_READ_FAILURE);
return net::ERR_IO_PENDING;
}
if (offset < 0 || offset > static_cast<int>(data_[index].size()))
return net::ERR_FAILED;
data_[index].resize(offset + buf_len);
if (buf_len)
memcpy(&data_[index][offset], buf->data(), buf_len);
if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_WRITE)
return buf_len;
CallbackLater(callback, buf_len);
return net::ERR_IO_PENDING;
}
int MockDiskEntry::ReadSparseData(int64 offset, net::IOBuffer* buf, int buf_len,
const net::CompletionCallback& callback) {
DCHECK(!callback.is_null());
if (fail_sparse_requests_)
return net::ERR_NOT_IMPLEMENTED;
if (!sparse_ || busy_)
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
if (offset < 0)
return net::ERR_FAILED;
if (fail_requests_)
return net::ERR_CACHE_READ_FAILURE;
DCHECK(offset < kint32max);
int real_offset = static_cast<int>(offset);
if (!buf_len)
return 0;
int num = std::min(static_cast<int>(data_[1].size()) - real_offset,
buf_len);
memcpy(buf->data(), &data_[1][real_offset], num);
if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_READ)
return num;
CallbackLater(callback, num);
busy_ = true;
delayed_ = false;
return net::ERR_IO_PENDING;
}
int MockDiskEntry::WriteSparseData(int64 offset, net::IOBuffer* buf,
int buf_len,
const net::CompletionCallback& callback) {
DCHECK(!callback.is_null());
if (fail_sparse_requests_)
return net::ERR_NOT_IMPLEMENTED;
if (busy_)
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
if (!sparse_) {
if (data_[1].size())
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
sparse_ = true;
}
if (offset < 0)
return net::ERR_FAILED;
if (!buf_len)
return 0;
if (fail_requests_)
return net::ERR_CACHE_READ_FAILURE;
DCHECK(offset < kint32max);
int real_offset = static_cast<int>(offset);
if (static_cast<int>(data_[1].size()) < real_offset + buf_len)
data_[1].resize(real_offset + buf_len);
memcpy(&data_[1][real_offset], buf->data(), buf_len);
if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_WRITE)
return buf_len;
CallbackLater(callback, buf_len);
return net::ERR_IO_PENDING;
}
int MockDiskEntry::GetAvailableRange(int64 offset, int len, int64* start,
const net::CompletionCallback& callback) {
DCHECK(!callback.is_null());
if (!sparse_ || busy_)
return net::ERR_CACHE_OPERATION_NOT_SUPPORTED;
if (offset < 0)
return net::ERR_FAILED;
if (fail_requests_)
return net::ERR_CACHE_READ_FAILURE;
*start = offset;
DCHECK(offset < kint32max);
int real_offset = static_cast<int>(offset);
if (static_cast<int>(data_[1].size()) < real_offset)
return 0;
int num = std::min(static_cast<int>(data_[1].size()) - real_offset, len);
int count = 0;
for (; num > 0; num--, real_offset++) {
if (!count) {
if (data_[1][real_offset]) {
count++;
*start = real_offset;
}
} else {
if (!data_[1][real_offset])
break;
count++;
}
}
if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_WRITE)
return count;
CallbackLater(callback, count);
return net::ERR_IO_PENDING;
}
bool MockDiskEntry::CouldBeSparse() const {
if (fail_sparse_requests_)
return false;
return sparse_;
}
void MockDiskEntry::CancelSparseIO() {
cancel_ = true;
}
int MockDiskEntry::ReadyForSparseIO(const net::CompletionCallback& callback) {
if (fail_sparse_requests_)
return net::ERR_NOT_IMPLEMENTED;
if (!cancel_)
return net::OK;
cancel_ = false;
DCHECK(!callback.is_null());
if (MockHttpCache::GetTestMode(test_mode_) & TEST_MODE_SYNC_CACHE_READ)
return net::OK;
// The pending operation is already in the message loop (and hopefully
// already in the second pass). Just notify the caller that it finished.
CallbackLater(callback, 0);
return net::ERR_IO_PENDING;
}
// If |value| is true, don't deliver any completion callbacks until called
// again with |value| set to false. Caution: remember to enable callbacks
// again or all subsequent tests will fail.
// Static.
void MockDiskEntry::IgnoreCallbacks(bool value) {
if (ignore_callbacks_ == value)
return;
ignore_callbacks_ = value;
if (!value)
StoreAndDeliverCallbacks(false, NULL, net::CompletionCallback(), 0);
}
MockDiskEntry::~MockDiskEntry() {
}
// Unlike the callbacks for MockHttpTransaction, we want this one to run even
// if the consumer called Close on the MockDiskEntry. We achieve that by
// leveraging the fact that this class is reference counted.
void MockDiskEntry::CallbackLater(const net::CompletionCallback& callback,
int result) {
if (ignore_callbacks_)
return StoreAndDeliverCallbacks(true, this, callback, result);
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&MockDiskEntry::RunCallback, this, callback, result));
}
void MockDiskEntry::RunCallback(
const net::CompletionCallback& callback, int result) {
if (busy_) {
// This is kind of hacky, but controlling the behavior of just this entry
// from a test is sort of complicated. What we really want to do is
// delay the delivery of a sparse IO operation a little more so that the
// request start operation (async) will finish without seeing the end of
// this operation (already posted to the message loop)... and without
// just delaying for n mS (which may cause trouble with slow bots). So
// we re-post this operation (all async sparse IO operations will take two
// trips through the message loop instead of one).
if (!delayed_) {
delayed_ = true;
return CallbackLater(callback, result);
}
}
busy_ = false;
callback.Run(result);
}
// When |store| is true, stores the callback to be delivered later; otherwise
// delivers any callback previously stored.
// Static.
void MockDiskEntry::StoreAndDeliverCallbacks(
bool store, MockDiskEntry* entry, const net::CompletionCallback& callback,
int result) {
static std::vector<CallbackInfo> callback_list;
if (store) {
CallbackInfo c = {entry, callback, result};
callback_list.push_back(c);
} else {
for (size_t i = 0; i < callback_list.size(); i++) {
CallbackInfo& c = callback_list[i];
c.entry->CallbackLater(c.callback, c.result);
}
callback_list.clear();
}
}
// Statics.
bool MockDiskEntry::cancel_ = false;
bool MockDiskEntry::ignore_callbacks_ = false;
//-----------------------------------------------------------------------------
MockDiskCache::MockDiskCache()
: open_count_(0), create_count_(0), fail_requests_(false),
soft_failures_(false), double_create_check_(true),
fail_sparse_requests_(false) {
}
MockDiskCache::~MockDiskCache() {
ReleaseAll();
}
net::CacheType MockDiskCache::GetCacheType() const {
return net::DISK_CACHE;
}
int32 MockDiskCache::GetEntryCount() const {
return static_cast<int32>(entries_.size());
}
int MockDiskCache::OpenEntry(const std::string& key, disk_cache::Entry** entry,
const net::CompletionCallback& callback) {
DCHECK(!callback.is_null());
if (fail_requests_)
return net::ERR_CACHE_OPEN_FAILURE;
EntryMap::iterator it = entries_.find(key);
if (it == entries_.end())
return net::ERR_CACHE_OPEN_FAILURE;
if (it->second->is_doomed()) {
it->second->Release();
entries_.erase(it);
return net::ERR_CACHE_OPEN_FAILURE;
}
open_count_++;
it->second->AddRef();
*entry = it->second;
if (soft_failures_)
it->second->set_fail_requests();
if (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START)
return net::OK;
CallbackLater(callback, net::OK);
return net::ERR_IO_PENDING;
}
int MockDiskCache::CreateEntry(const std::string& key,
disk_cache::Entry** entry,
const net::CompletionCallback& callback) {
DCHECK(!callback.is_null());
if (fail_requests_)
return net::ERR_CACHE_CREATE_FAILURE;
EntryMap::iterator it = entries_.find(key);
if (it != entries_.end()) {
if (!it->second->is_doomed()) {
if (double_create_check_)
NOTREACHED();
else
return net::ERR_CACHE_CREATE_FAILURE;
}
it->second->Release();
entries_.erase(it);
}
create_count_++;
MockDiskEntry* new_entry = new MockDiskEntry(key);
new_entry->AddRef();
entries_[key] = new_entry;
new_entry->AddRef();
*entry = new_entry;
if (soft_failures_)
new_entry->set_fail_requests();
if (fail_sparse_requests_)
new_entry->set_fail_sparse_requests();
if (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START)
return net::OK;
CallbackLater(callback, net::OK);
return net::ERR_IO_PENDING;
}
int MockDiskCache::DoomEntry(const std::string& key,
const net::CompletionCallback& callback) {
DCHECK(!callback.is_null());
EntryMap::iterator it = entries_.find(key);
if (it != entries_.end()) {
it->second->Release();
entries_.erase(it);
}
if (GetTestModeForEntry(key) & TEST_MODE_SYNC_CACHE_START)
return net::OK;
CallbackLater(callback, net::OK);
return net::ERR_IO_PENDING;
}
int MockDiskCache::DoomAllEntries(const net::CompletionCallback& callback) {
return net::ERR_NOT_IMPLEMENTED;
}
int MockDiskCache::DoomEntriesBetween(const base::Time initial_time,
const base::Time end_time,
const net::CompletionCallback& callback) {
return net::ERR_NOT_IMPLEMENTED;
}
int MockDiskCache::DoomEntriesSince(const base::Time initial_time,
const net::CompletionCallback& callback) {
return net::ERR_NOT_IMPLEMENTED;
}
int MockDiskCache::OpenNextEntry(void** iter, disk_cache::Entry** next_entry,
const net::CompletionCallback& callback) {
return net::ERR_NOT_IMPLEMENTED;
}
void MockDiskCache::EndEnumeration(void** iter) {
}
void MockDiskCache::GetStats(
std::vector<std::pair<std::string, std::string> >* stats) {
}
void MockDiskCache::OnExternalCacheHit(const std::string& key) {
}
void MockDiskCache::ReleaseAll() {
EntryMap::iterator it = entries_.begin();
for (; it != entries_.end(); ++it)
it->second->Release();
entries_.clear();
}
void MockDiskCache::CallbackLater(const net::CompletionCallback& callback,
int result) {
base::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&CallbackForwader, callback, result));
}
//-----------------------------------------------------------------------------
int MockBackendFactory::CreateBackend(net::NetLog* net_log,
scoped_ptr<disk_cache::Backend>* backend,
const net::CompletionCallback& callback) {
backend->reset(new MockDiskCache());
return net::OK;
}
//-----------------------------------------------------------------------------
MockHttpCache::MockHttpCache()
: http_cache_(new MockNetworkLayer(), NULL, new MockBackendFactory()) {
}
MockHttpCache::MockHttpCache(net::HttpCache::BackendFactory* disk_cache_factory)
: http_cache_(new MockNetworkLayer(), NULL, disk_cache_factory) {
}
MockDiskCache* MockHttpCache::disk_cache() {
net::TestCompletionCallback cb;
disk_cache::Backend* backend;
int rv = http_cache_.GetBackend(&backend, cb.callback());
rv = cb.GetResult(rv);
return (rv == net::OK) ? static_cast<MockDiskCache*>(backend) : NULL;
}
bool MockHttpCache::ReadResponseInfo(disk_cache::Entry* disk_entry,
net::HttpResponseInfo* response_info,
bool* response_truncated) {
int size = disk_entry->GetDataSize(0);
net::TestCompletionCallback cb;
scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(size));
int rv = disk_entry->ReadData(0, 0, buffer.get(), size, cb.callback());
rv = cb.GetResult(rv);
EXPECT_EQ(size, rv);
return net::HttpCache::ParseResponseInfo(buffer->data(), size,
response_info,
response_truncated);
}
bool MockHttpCache::WriteResponseInfo(
disk_cache::Entry* disk_entry, const net::HttpResponseInfo* response_info,
bool skip_transient_headers, bool response_truncated) {
Pickle pickle;
response_info->Persist(
&pickle, skip_transient_headers, response_truncated);
net::TestCompletionCallback cb;
scoped_refptr<net::WrappedIOBuffer> data(new net::WrappedIOBuffer(
reinterpret_cast<const char*>(pickle.data())));
int len = static_cast<int>(pickle.size());
int rv = disk_entry->WriteData(0, 0, data.get(), len, cb.callback(), true);
rv = cb.GetResult(rv);
return (rv == len);
}
bool MockHttpCache::OpenBackendEntry(const std::string& key,
disk_cache::Entry** entry) {
net::TestCompletionCallback cb;
int rv = disk_cache()->OpenEntry(key, entry, cb.callback());
return (cb.GetResult(rv) == net::OK);
}
bool MockHttpCache::CreateBackendEntry(const std::string& key,
disk_cache::Entry** entry,
net::NetLog* net_log) {
net::TestCompletionCallback cb;
int rv = disk_cache()->CreateEntry(key, entry, cb.callback());
return (cb.GetResult(rv) == net::OK);
}
// Static.
int MockHttpCache::GetTestMode(int test_mode) {
if (!g_test_mode)
return test_mode;
return g_test_mode;
}
// Static.
void MockHttpCache::SetTestMode(int test_mode) {
g_test_mode = test_mode;
}
//-----------------------------------------------------------------------------
int MockDiskCacheNoCB::CreateEntry(const std::string& key,
disk_cache::Entry** entry,
const net::CompletionCallback& callback) {
return net::ERR_IO_PENDING;
}
//-----------------------------------------------------------------------------
int MockBackendNoCbFactory::CreateBackend(
net::NetLog* net_log, scoped_ptr<disk_cache::Backend>* backend,
const net::CompletionCallback& callback) {
backend->reset(new MockDiskCacheNoCB());
return net::OK;
}
//-----------------------------------------------------------------------------
MockBlockingBackendFactory::MockBlockingBackendFactory()
: backend_(NULL),
block_(true),
fail_(false) {
}
MockBlockingBackendFactory::~MockBlockingBackendFactory() {
}
int MockBlockingBackendFactory::CreateBackend(
net::NetLog* net_log, scoped_ptr<disk_cache::Backend>* backend,
const net::CompletionCallback& callback) {
if (!block_) {
if (!fail_)
backend->reset(new MockDiskCache());
return Result();
}
backend_ = backend;
callback_ = callback;
return net::ERR_IO_PENDING;
}
void MockBlockingBackendFactory::FinishCreation() {
block_ = false;
if (!callback_.is_null()) {
if (!fail_)
backend_->reset(new MockDiskCache());
net::CompletionCallback cb = callback_;
callback_.Reset();
cb.Run(Result()); // This object can be deleted here.
}
}