841 lines
27 KiB
C++
841 lines
27 KiB
C++
// Copyright 2017 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/http/http_cache_writers.h"
|
|
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "base/functional/bind.h"
|
|
#include "base/memory/raw_ptr_exclusion.h"
|
|
#include "base/run_loop.h"
|
|
#include "crypto/secure_hash.h"
|
|
#include "net/http/http_cache.h"
|
|
#include "net/http/http_cache_transaction.h"
|
|
#include "net/http/http_response_info.h"
|
|
#include "net/http/http_transaction.h"
|
|
#include "net/http/http_transaction_test_util.h"
|
|
#include "net/http/mock_http_cache.h"
|
|
#include "net/http/partial_data.h"
|
|
#include "net/test/gtest_util.h"
|
|
#include "net/test/test_with_task_environment.h"
|
|
#include "testing/gmock/include/gmock/gmock.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
using net::test::IsError;
|
|
using net::test::IsOk;
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
// Helper function, generating valid HTTP cache key from `url`.
|
|
// See also: HttpCache::GenerateCacheKey(..)
|
|
std::string GenerateCacheKey(const std::string& url) {
|
|
return "1/0/" + url;
|
|
}
|
|
} // namespace
|
|
|
|
class WritersTest;
|
|
|
|
class TestHttpCacheTransaction : public HttpCache::Transaction {
|
|
typedef WebSocketHandshakeStreamBase::CreateHelper CreateHelper;
|
|
|
|
public:
|
|
TestHttpCacheTransaction(RequestPriority priority, HttpCache* cache)
|
|
: HttpCache::Transaction(priority, cache) {}
|
|
~TestHttpCacheTransaction() override = default;
|
|
|
|
Transaction::Mode mode() const override { return Transaction::READ_WRITE; }
|
|
};
|
|
|
|
class TestHttpCache : public HttpCache {
|
|
public:
|
|
TestHttpCache(std::unique_ptr<HttpTransactionFactory> network_layer,
|
|
std::unique_ptr<BackendFactory> backend_factory)
|
|
: HttpCache(std::move(network_layer), std::move(backend_factory)) {}
|
|
|
|
void WritersDoneWritingToEntry(ActiveEntry* entry,
|
|
bool success,
|
|
bool should_keep_entry,
|
|
TransactionSet make_readers) override {
|
|
done_writing_to_entry_count_ += 1;
|
|
make_readers_size_ = make_readers.size();
|
|
}
|
|
|
|
void WritersDoomEntryRestartTransactions(ActiveEntry* entry) override {}
|
|
|
|
int WritersDoneWritingToEntryCount() const {
|
|
return done_writing_to_entry_count_;
|
|
}
|
|
|
|
size_t MakeReadersSize() const { return make_readers_size_; }
|
|
|
|
private:
|
|
int done_writing_to_entry_count_ = 0;
|
|
size_t make_readers_size_ = 0u;
|
|
};
|
|
|
|
class WritersTest : public TestWithTaskEnvironment {
|
|
public:
|
|
enum class DeleteTransactionType { NONE, ACTIVE, WAITING, IDLE };
|
|
WritersTest()
|
|
: scoped_transaction_(kSimpleGET_Transaction),
|
|
test_cache_(std::make_unique<MockNetworkLayer>(),
|
|
std::make_unique<MockBackendFactory>()),
|
|
request_(kSimpleGET_Transaction) {
|
|
scoped_transaction_.response_headers =
|
|
"Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n"
|
|
"Content-Length: 22\n"
|
|
"Etag: \"foopy\"\n";
|
|
request_ = MockHttpRequest(scoped_transaction_);
|
|
}
|
|
|
|
~WritersTest() override {
|
|
if (disk_entry_)
|
|
disk_entry_->Close();
|
|
}
|
|
|
|
void CreateWriters() {
|
|
cache_.CreateBackendEntry(GenerateCacheKey(kSimpleGET_Transaction.url),
|
|
&disk_entry_, nullptr);
|
|
entry_ = std::make_unique<HttpCache::ActiveEntry>(disk_entry_, false);
|
|
(static_cast<MockDiskEntry*>(disk_entry_))->AddRef();
|
|
writers_ = std::make_unique<HttpCache::Writers>(&test_cache_, entry_.get());
|
|
}
|
|
|
|
std::unique_ptr<HttpTransaction> CreateNetworkTransaction() {
|
|
std::unique_ptr<HttpTransaction> transaction;
|
|
MockNetworkLayer* network_layer = cache_.network_layer();
|
|
network_layer->CreateTransaction(DEFAULT_PRIORITY, &transaction);
|
|
return transaction;
|
|
}
|
|
|
|
void CreateWritersAddTransaction(
|
|
HttpCache::ParallelWritingPattern parallel_writing_pattern_ =
|
|
HttpCache::PARALLEL_WRITING_JOIN,
|
|
bool content_encoding_present = false) {
|
|
TestCompletionCallback callback;
|
|
|
|
// Create and Start a mock network transaction.
|
|
std::unique_ptr<HttpTransaction> network_transaction;
|
|
network_transaction = CreateNetworkTransaction();
|
|
network_transaction->Start(&request_, callback.callback(),
|
|
NetLogWithSource());
|
|
base::RunLoop().RunUntilIdle();
|
|
response_info_ = *(network_transaction->GetResponseInfo());
|
|
if (content_encoding_present)
|
|
response_info_.headers->AddHeader("Content-Encoding", "gzip");
|
|
|
|
// Create a mock cache transaction.
|
|
std::unique_ptr<TestHttpCacheTransaction> transaction =
|
|
std::make_unique<TestHttpCacheTransaction>(DEFAULT_PRIORITY,
|
|
cache_.http_cache());
|
|
|
|
CreateWriters();
|
|
EXPECT_TRUE(writers_->IsEmpty());
|
|
HttpCache::Writers::TransactionInfo info(
|
|
transaction->partial(), transaction->is_truncated(), response_info_);
|
|
|
|
writers_->AddTransaction(transaction.get(), parallel_writing_pattern_,
|
|
transaction->priority(), info);
|
|
writers_->SetNetworkTransaction(transaction.get(),
|
|
std::move(network_transaction), nullptr);
|
|
EXPECT_TRUE(writers_->HasTransaction(transaction.get()));
|
|
transactions_.push_back(std::move(transaction));
|
|
}
|
|
|
|
void CreateWritersAddTransactionPriority(
|
|
net::RequestPriority priority,
|
|
HttpCache::ParallelWritingPattern parallel_writing_pattern_ =
|
|
HttpCache::PARALLEL_WRITING_JOIN) {
|
|
CreateWritersAddTransaction(parallel_writing_pattern_);
|
|
TestHttpCacheTransaction* transaction = transactions_.begin()->get();
|
|
transaction->SetPriority(priority);
|
|
}
|
|
|
|
void AddTransactionToExistingWriters() {
|
|
EXPECT_TRUE(writers_);
|
|
|
|
// Create a mock cache transaction.
|
|
std::unique_ptr<TestHttpCacheTransaction> transaction =
|
|
std::make_unique<TestHttpCacheTransaction>(DEFAULT_PRIORITY,
|
|
cache_.http_cache());
|
|
|
|
HttpCache::Writers::TransactionInfo info(transaction->partial(),
|
|
transaction->is_truncated(),
|
|
*(transaction->GetResponseInfo()));
|
|
info.response_info = response_info_;
|
|
writers_->AddTransaction(transaction.get(),
|
|
HttpCache::PARALLEL_WRITING_JOIN,
|
|
transaction->priority(), info);
|
|
transactions_.push_back(std::move(transaction));
|
|
}
|
|
|
|
int Read(std::string* result) {
|
|
EXPECT_TRUE(transactions_.size() >= (size_t)1);
|
|
TestHttpCacheTransaction* transaction = transactions_.begin()->get();
|
|
TestCompletionCallback callback;
|
|
|
|
std::string content;
|
|
int rv = 0;
|
|
do {
|
|
scoped_refptr<IOBuffer> buf =
|
|
base::MakeRefCounted<IOBuffer>(kDefaultBufferSize);
|
|
rv = writers_->Read(buf.get(), kDefaultBufferSize, callback.callback(),
|
|
transaction);
|
|
if (rv == ERR_IO_PENDING) {
|
|
rv = callback.WaitForResult();
|
|
base::RunLoop().RunUntilIdle();
|
|
}
|
|
|
|
if (rv > 0)
|
|
content.append(buf->data(), rv);
|
|
else if (rv < 0)
|
|
return rv;
|
|
} while (rv > 0);
|
|
|
|
result->swap(content);
|
|
return OK;
|
|
}
|
|
|
|
int ReadFewBytes(std::string* result) {
|
|
EXPECT_TRUE(transactions_.size() >= (size_t)1);
|
|
TestHttpCacheTransaction* transaction = transactions_.begin()->get();
|
|
TestCompletionCallback callback;
|
|
|
|
std::string content;
|
|
int rv = 0;
|
|
scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(5);
|
|
rv = writers_->Read(buf.get(), 5, callback.callback(), transaction);
|
|
if (rv == ERR_IO_PENDING) {
|
|
rv = callback.WaitForResult();
|
|
base::RunLoop().RunUntilIdle();
|
|
}
|
|
|
|
if (rv > 0)
|
|
result->append(buf->data(), rv);
|
|
else if (rv < 0)
|
|
return rv;
|
|
|
|
return OK;
|
|
}
|
|
|
|
void ReadVerifyTwoDifferentBufferLengths(
|
|
const std::vector<int>& buffer_lengths) {
|
|
EXPECT_EQ(2u, buffer_lengths.size());
|
|
EXPECT_EQ(2u, transactions_.size());
|
|
|
|
std::vector<std::string> results(buffer_lengths.size());
|
|
|
|
// Check only the 1st Read and not the complete response because the smaller
|
|
// buffer transaction will need to read the remaining response from the
|
|
// cache which will be tested when integrated with TestHttpCacheTransaction
|
|
// layer.
|
|
|
|
int rv = 0;
|
|
|
|
std::vector<scoped_refptr<IOBuffer>> bufs;
|
|
for (auto buffer_length : buffer_lengths)
|
|
bufs.push_back(base::MakeRefCounted<IOBuffer>(buffer_length));
|
|
|
|
std::vector<TestCompletionCallback> callbacks(buffer_lengths.size());
|
|
|
|
// Multiple transactions should be able to read with different sized
|
|
// buffers.
|
|
for (size_t i = 0; i < transactions_.size(); i++) {
|
|
rv = writers_->Read(bufs[i].get(), buffer_lengths[i],
|
|
callbacks[i].callback(), transactions_[i].get());
|
|
EXPECT_EQ(ERR_IO_PENDING, rv); // Since the default is asynchronous.
|
|
}
|
|
|
|
// If first buffer is smaller, then the second one will only read the
|
|
// smaller length as well.
|
|
std::vector<int> expected_lengths = {buffer_lengths[0],
|
|
buffer_lengths[0] < buffer_lengths[1]
|
|
? buffer_lengths[0]
|
|
: buffer_lengths[1]};
|
|
|
|
for (size_t i = 0; i < callbacks.size(); i++) {
|
|
rv = callbacks[i].WaitForResult();
|
|
EXPECT_EQ(expected_lengths[i], rv);
|
|
results[i].append(bufs[i]->data(), expected_lengths[i]);
|
|
}
|
|
|
|
EXPECT_EQ(results[0].substr(0, expected_lengths[1]), results[1]);
|
|
|
|
std::string expected(kSimpleGET_Transaction.data);
|
|
EXPECT_EQ(expected.substr(0, expected_lengths[1]), results[1]);
|
|
}
|
|
|
|
// Each transaction invokes Read simultaneously. If |deleteType| is not NONE,
|
|
// then it deletes the transaction of given type during the read process.
|
|
void ReadAllDeleteTransaction(DeleteTransactionType deleteType) {
|
|
EXPECT_LE(3u, transactions_.size());
|
|
|
|
unsigned int delete_index = std::numeric_limits<unsigned int>::max();
|
|
switch (deleteType) {
|
|
case DeleteTransactionType::NONE:
|
|
break;
|
|
case DeleteTransactionType::ACTIVE:
|
|
delete_index = 0;
|
|
break;
|
|
case DeleteTransactionType::WAITING:
|
|
delete_index = 1;
|
|
break;
|
|
case DeleteTransactionType::IDLE:
|
|
delete_index = 2;
|
|
break;
|
|
}
|
|
|
|
std::vector<std::string> results(transactions_.size());
|
|
int rv = 0;
|
|
bool first_iter = true;
|
|
do {
|
|
std::vector<scoped_refptr<IOBuffer>> bufs;
|
|
std::vector<TestCompletionCallback> callbacks(transactions_.size());
|
|
|
|
for (size_t i = 0; i < transactions_.size(); i++) {
|
|
bufs.push_back(base::MakeRefCounted<IOBuffer>(kDefaultBufferSize));
|
|
|
|
// If we have deleted a transaction in the first iteration, then do not
|
|
// invoke Read on it, in subsequent iterations.
|
|
if (!first_iter && deleteType != DeleteTransactionType::NONE &&
|
|
i == delete_index)
|
|
continue;
|
|
|
|
// For it to be an idle transaction, do not invoke Read.
|
|
if (deleteType == DeleteTransactionType::IDLE && i == delete_index)
|
|
continue;
|
|
|
|
rv = writers_->Read(bufs[i].get(), kDefaultBufferSize,
|
|
callbacks[i].callback(), transactions_[i].get());
|
|
EXPECT_EQ(ERR_IO_PENDING, rv); // Since the default is asynchronous.
|
|
}
|
|
|
|
if (first_iter && deleteType != DeleteTransactionType::NONE) {
|
|
writers_->RemoveTransaction(transactions_.at(delete_index).get(),
|
|
false /* success */);
|
|
}
|
|
|
|
// Verify Add Transaction should succeed mid-read.
|
|
AddTransactionToExistingWriters();
|
|
|
|
std::vector<int> rvs;
|
|
for (size_t i = 0; i < callbacks.size(); i++) {
|
|
if (i == delete_index && deleteType != DeleteTransactionType::NONE)
|
|
continue;
|
|
rv = callbacks[i].WaitForResult();
|
|
rvs.push_back(rv);
|
|
}
|
|
|
|
// Verify all transactions should read the same length buffer.
|
|
for (size_t i = 1; i < rvs.size(); i++) {
|
|
ASSERT_EQ(rvs[i - 1], rvs[i]);
|
|
}
|
|
|
|
if (rv > 0) {
|
|
for (size_t i = 0; i < results.size(); i++) {
|
|
if (i == delete_index && deleteType != DeleteTransactionType::NONE &&
|
|
deleteType != DeleteTransactionType::ACTIVE) {
|
|
continue;
|
|
}
|
|
results.at(i).append(bufs[i]->data(), rv);
|
|
}
|
|
}
|
|
first_iter = false;
|
|
} while (rv > 0);
|
|
|
|
for (size_t i = 0; i < results.size(); i++) {
|
|
if (i == delete_index && deleteType != DeleteTransactionType::NONE &&
|
|
deleteType != DeleteTransactionType::ACTIVE) {
|
|
continue;
|
|
}
|
|
EXPECT_EQ(kSimpleGET_Transaction.data, results[i]);
|
|
}
|
|
|
|
EXPECT_EQ(OK, rv);
|
|
}
|
|
|
|
// Creates a transaction and performs two reads. Returns after the second read
|
|
// has begun but before its callback has run.
|
|
void StopMidRead() {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
EXPECT_EQ(1u, transactions_.size());
|
|
TestHttpCacheTransaction* transaction = transactions_[0].get();
|
|
|
|
// Read a few bytes so that truncation is possible.
|
|
TestCompletionCallback callback;
|
|
scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(5);
|
|
int rv = writers_->Read(buf.get(), 5, callback.callback(), transaction);
|
|
EXPECT_EQ(ERR_IO_PENDING, rv); // Since the default is asynchronous.
|
|
EXPECT_EQ(5, callback.GetResult(rv));
|
|
|
|
// Start reading a few more bytes and return.
|
|
buf = base::MakeRefCounted<IOBuffer>(5);
|
|
rv = writers_->Read(buf.get(), 5, base::BindOnce([](int rv) {}),
|
|
transaction);
|
|
EXPECT_EQ(ERR_IO_PENDING, rv);
|
|
}
|
|
|
|
void ReadAll() { ReadAllDeleteTransaction(DeleteTransactionType::NONE); }
|
|
|
|
int ReadCacheWriteFailure(std::vector<std::string>* results) {
|
|
int rv = 0;
|
|
int active_transaction_rv = 0;
|
|
bool first_iter = true;
|
|
do {
|
|
std::vector<scoped_refptr<IOBuffer>> bufs;
|
|
std::vector<TestCompletionCallback> callbacks(results->size());
|
|
|
|
// Fail the request.
|
|
cache_.disk_cache()->set_soft_failures_mask(MockDiskEntry::FAIL_ALL);
|
|
|
|
// We have to open the entry again to propagate the failure flag.
|
|
disk_cache::Entry* en;
|
|
cache_.OpenBackendEntry(GenerateCacheKey(kSimpleGET_Transaction.url),
|
|
&en);
|
|
en->Close();
|
|
|
|
for (size_t i = 0; i < transactions_.size(); i++) {
|
|
bufs.push_back(base::MakeRefCounted<IOBuffer>(30));
|
|
|
|
if (!first_iter && i > 0)
|
|
break;
|
|
rv = writers_->Read(bufs[i].get(), 30, callbacks[i].callback(),
|
|
transactions_[i].get());
|
|
EXPECT_EQ(ERR_IO_PENDING, rv); // Since the default is asynchronous.
|
|
}
|
|
|
|
for (size_t i = 0; i < callbacks.size(); i++) {
|
|
// Only active transaction should succeed.
|
|
if (i == 0) {
|
|
active_transaction_rv = callbacks[i].WaitForResult();
|
|
EXPECT_LE(0, active_transaction_rv);
|
|
results->at(0).append(bufs[i]->data(), active_transaction_rv);
|
|
} else if (first_iter) {
|
|
rv = callbacks[i].WaitForResult();
|
|
EXPECT_EQ(ERR_CACHE_WRITE_FAILURE, rv);
|
|
}
|
|
}
|
|
|
|
first_iter = false;
|
|
} while (active_transaction_rv > 0);
|
|
|
|
return active_transaction_rv;
|
|
}
|
|
|
|
int ReadNetworkFailure(std::vector<std::string>* results, Error error) {
|
|
int rv = 0;
|
|
std::vector<scoped_refptr<IOBuffer>> bufs;
|
|
std::vector<TestCompletionCallback> callbacks(results->size());
|
|
|
|
for (size_t i = 0; i < transactions_.size(); i++) {
|
|
bufs.push_back(base::MakeRefCounted<IOBuffer>(30));
|
|
|
|
rv = writers_->Read(bufs[i].get(), 30, callbacks[i].callback(),
|
|
transactions_[i].get());
|
|
EXPECT_EQ(ERR_IO_PENDING, rv); // Since the default is asynchronous.
|
|
}
|
|
|
|
for (auto& callback : callbacks) {
|
|
rv = callback.WaitForResult();
|
|
EXPECT_EQ(error, rv);
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
bool StopCaching() {
|
|
TestHttpCacheTransaction* transaction = transactions_.begin()->get();
|
|
EXPECT_TRUE(transaction);
|
|
return writers_->StopCaching(transaction);
|
|
}
|
|
|
|
void RemoveFirstTransaction() {
|
|
TestHttpCacheTransaction* transaction = transactions_.begin()->get();
|
|
EXPECT_TRUE(transaction);
|
|
writers_->RemoveTransaction(transaction, false /* success */);
|
|
}
|
|
|
|
void UpdateAndVerifyPriority(RequestPriority priority) {
|
|
writers_->UpdatePriority();
|
|
EXPECT_EQ(priority, writers_->priority_);
|
|
}
|
|
|
|
bool ShouldKeepEntry() const { return writers_->should_keep_entry_; }
|
|
|
|
bool Truncated() const {
|
|
const int kResponseInfoIndex = 0; // Keep updated with HttpCache.
|
|
TestCompletionCallback callback;
|
|
int io_buf_len = entry_->disk_entry->GetDataSize(kResponseInfoIndex);
|
|
if (io_buf_len == 0)
|
|
return false;
|
|
|
|
scoped_refptr<IOBuffer> read_buffer =
|
|
base::MakeRefCounted<IOBuffer>(io_buf_len);
|
|
int rv = disk_entry_->ReadData(kResponseInfoIndex, 0, read_buffer.get(),
|
|
io_buf_len, callback.callback());
|
|
rv = callback.GetResult(rv);
|
|
HttpResponseInfo response_info;
|
|
bool truncated;
|
|
HttpCache::ParseResponseInfo(read_buffer->data(), io_buf_len,
|
|
&response_info, &truncated);
|
|
return truncated;
|
|
}
|
|
|
|
bool ShouldTruncate() { return writers_->ShouldTruncate(); }
|
|
|
|
bool CanAddWriters() {
|
|
HttpCache::ParallelWritingPattern parallel_writing_pattern_;
|
|
return writers_->CanAddWriters(¶llel_writing_pattern_);
|
|
}
|
|
|
|
ScopedMockTransaction scoped_transaction_;
|
|
MockHttpCache cache_;
|
|
std::unique_ptr<HttpCache::Writers> writers_;
|
|
// This field is not a raw_ptr<> because it was filtered by the rewriter for:
|
|
// #addr-of
|
|
RAW_PTR_EXCLUSION disk_cache::Entry* disk_entry_ = nullptr;
|
|
std::unique_ptr<HttpCache::ActiveEntry> entry_;
|
|
TestHttpCache test_cache_;
|
|
|
|
// Should be before transactions_ since it is accessed in the network
|
|
// transaction's destructor.
|
|
MockHttpRequest request_;
|
|
|
|
HttpResponseInfo response_info_;
|
|
static const int kDefaultBufferSize = 256;
|
|
|
|
std::vector<std::unique_ptr<TestHttpCacheTransaction>> transactions_;
|
|
};
|
|
|
|
const int WritersTest::kDefaultBufferSize;
|
|
|
|
// Tests successful addition of a transaction.
|
|
TEST_F(WritersTest, AddTransaction) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
// Verify keep_entry_ is true by default.
|
|
EXPECT_TRUE(ShouldKeepEntry());
|
|
}
|
|
|
|
// Tests successful addition of multiple transactions.
|
|
TEST_F(WritersTest, AddManyTransactions) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
for (int i = 0; i < 5; i++)
|
|
AddTransactionToExistingWriters();
|
|
|
|
EXPECT_EQ(6, writers_->GetTransactionsCount());
|
|
}
|
|
|
|
// Tests that CanAddWriters should return false if it is writing exclusively.
|
|
TEST_F(WritersTest, AddTransactionsExclusive) {
|
|
CreateWritersAddTransaction(HttpCache::PARALLEL_WRITING_NOT_JOIN_RANGE);
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_FALSE(CanAddWriters());
|
|
}
|
|
|
|
// Tests StopCaching should not stop caching if there are multiple writers.
|
|
TEST_F(WritersTest, StopCachingMultipleWriters) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(CanAddWriters());
|
|
AddTransactionToExistingWriters();
|
|
|
|
EXPECT_FALSE(StopCaching());
|
|
EXPECT_TRUE(CanAddWriters());
|
|
}
|
|
|
|
// Tests StopCaching should stop caching if there is a single writer.
|
|
TEST_F(WritersTest, StopCaching) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(StopCaching());
|
|
EXPECT_FALSE(CanAddWriters());
|
|
}
|
|
|
|
// Tests that when the writers object completes, it passes any non-pending
|
|
// transactions to WritersDoneWritingToEntry.
|
|
TEST_F(WritersTest, MakeReaders) {
|
|
CreateWritersAddTransaction();
|
|
AddTransactionToExistingWriters();
|
|
AddTransactionToExistingWriters();
|
|
|
|
std::string remaining_content;
|
|
Read(&remaining_content);
|
|
|
|
EXPECT_EQ(1, test_cache_.WritersDoneWritingToEntryCount());
|
|
EXPECT_FALSE(Truncated());
|
|
EXPECT_EQ(2u, test_cache_.MakeReadersSize());
|
|
}
|
|
|
|
// Tests StopCaching should be successful when invoked mid-read.
|
|
TEST_F(WritersTest, StopCachingMidReadKeepEntry) {
|
|
StopMidRead();
|
|
|
|
// Stop caching and keep the entry after the transaction finishes.
|
|
writers_->StopCaching(true /* keep_entry */);
|
|
|
|
// Cannot add more writers while we are in network read-only state.
|
|
EXPECT_FALSE(CanAddWriters());
|
|
|
|
// Complete the pending read;
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Read the rest of the content and the cache entry should have truncated.
|
|
std::string remaining_content;
|
|
Read(&remaining_content);
|
|
EXPECT_EQ(1, test_cache_.WritersDoneWritingToEntryCount());
|
|
EXPECT_TRUE(Truncated());
|
|
}
|
|
|
|
// Tests StopCaching should be successful when invoked mid-read.
|
|
TEST_F(WritersTest, StopCachingMidReadDropEntry) {
|
|
StopMidRead();
|
|
|
|
writers_->StopCaching(false /* keep_entry */);
|
|
|
|
// Cannot add more writers while we are in network read only state.
|
|
EXPECT_FALSE(CanAddWriters());
|
|
|
|
// Complete the pending read.
|
|
base::RunLoop().RunUntilIdle();
|
|
|
|
// Read the rest of the content and the cache entry shouldn't have truncated.
|
|
std::string remaining_content;
|
|
Read(&remaining_content);
|
|
EXPECT_EQ(1, test_cache_.WritersDoneWritingToEntryCount());
|
|
EXPECT_FALSE(Truncated());
|
|
}
|
|
|
|
// Tests removing of an idle transaction and change in priority.
|
|
TEST_F(WritersTest, RemoveIdleTransaction) {
|
|
CreateWritersAddTransactionPriority(HIGHEST);
|
|
UpdateAndVerifyPriority(HIGHEST);
|
|
|
|
AddTransactionToExistingWriters();
|
|
UpdateAndVerifyPriority(HIGHEST);
|
|
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
EXPECT_EQ(2, writers_->GetTransactionsCount());
|
|
|
|
RemoveFirstTransaction();
|
|
EXPECT_EQ(1, writers_->GetTransactionsCount());
|
|
|
|
UpdateAndVerifyPriority(DEFAULT_PRIORITY);
|
|
}
|
|
|
|
// Tests that Read is successful.
|
|
TEST_F(WritersTest, Read) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
std::string content;
|
|
int rv = Read(&content);
|
|
|
|
EXPECT_THAT(rv, IsOk());
|
|
std::string expected(kSimpleGET_Transaction.data);
|
|
EXPECT_EQ(expected, content);
|
|
}
|
|
|
|
// Tests that multiple transactions can read the same data simultaneously.
|
|
TEST_F(WritersTest, ReadMultiple) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(CanAddWriters());
|
|
AddTransactionToExistingWriters();
|
|
AddTransactionToExistingWriters();
|
|
|
|
ReadAll();
|
|
|
|
EXPECT_EQ(1, test_cache_.WritersDoneWritingToEntryCount());
|
|
}
|
|
|
|
// Tests that multiple transactions can read the same data simultaneously.
|
|
TEST_F(WritersTest, ReadMultipleDifferentBufferSizes) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(CanAddWriters());
|
|
AddTransactionToExistingWriters();
|
|
|
|
std::vector<int> buffer_lengths{20, 10};
|
|
ReadVerifyTwoDifferentBufferLengths(buffer_lengths);
|
|
}
|
|
|
|
// Same as above but tests the first transaction having smaller buffer size
|
|
// than the next.
|
|
TEST_F(WritersTest, ReadMultipleDifferentBufferSizes1) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(CanAddWriters());
|
|
AddTransactionToExistingWriters();
|
|
|
|
std::vector<int> buffer_lengths{10, 20};
|
|
ReadVerifyTwoDifferentBufferLengths(buffer_lengths);
|
|
}
|
|
|
|
// Tests that ongoing Read completes even when active transaction is deleted
|
|
// mid-read. Any transactions waiting should be able to get the read buffer.
|
|
TEST_F(WritersTest, ReadMultipleDeleteActiveTransaction) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(CanAddWriters());
|
|
AddTransactionToExistingWriters();
|
|
AddTransactionToExistingWriters();
|
|
|
|
ReadAllDeleteTransaction(DeleteTransactionType::ACTIVE);
|
|
EXPECT_EQ(1, test_cache_.WritersDoneWritingToEntryCount());
|
|
}
|
|
|
|
// Tests that ongoing Read is ignored when an active transaction is deleted
|
|
// mid-read and there are no more transactions. It should also successfully
|
|
// initiate truncation of the entry.
|
|
TEST_F(WritersTest, MidReadDeleteActiveTransaction) {
|
|
StopMidRead();
|
|
|
|
// Removed the transaction while the read is pending.
|
|
RemoveFirstTransaction();
|
|
|
|
EXPECT_EQ(1, test_cache_.WritersDoneWritingToEntryCount());
|
|
EXPECT_TRUE(Truncated());
|
|
EXPECT_TRUE(writers_->IsEmpty());
|
|
}
|
|
|
|
// Tests that removing a waiting for read transaction does not impact other
|
|
// transactions.
|
|
TEST_F(WritersTest, ReadMultipleDeleteWaitingTransaction) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(CanAddWriters());
|
|
AddTransactionToExistingWriters();
|
|
AddTransactionToExistingWriters();
|
|
AddTransactionToExistingWriters();
|
|
|
|
std::vector<std::string> contents(4);
|
|
ReadAllDeleteTransaction(DeleteTransactionType::WAITING);
|
|
}
|
|
|
|
// Tests that removing an idle transaction does not impact other transactions.
|
|
TEST_F(WritersTest, ReadMultipleDeleteIdleTransaction) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(CanAddWriters());
|
|
AddTransactionToExistingWriters();
|
|
AddTransactionToExistingWriters();
|
|
|
|
std::vector<std::string> contents(3);
|
|
ReadAllDeleteTransaction(DeleteTransactionType::IDLE);
|
|
}
|
|
|
|
// Tests cache write failure.
|
|
TEST_F(WritersTest, ReadMultipleCacheWriteFailed) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(CanAddWriters());
|
|
AddTransactionToExistingWriters();
|
|
AddTransactionToExistingWriters();
|
|
|
|
std::vector<std::string> contents(3);
|
|
int rv = ReadCacheWriteFailure(&contents);
|
|
|
|
EXPECT_THAT(rv, IsOk());
|
|
std::string expected(kSimpleGET_Transaction.data);
|
|
|
|
// Only active_transaction_ should succeed.
|
|
EXPECT_EQ(expected, contents.at(0));
|
|
}
|
|
|
|
// Tests that network read failure fails all transactions: active, waiting and
|
|
// idle.
|
|
TEST_F(WritersTest, ReadMultipleNetworkReadFailed) {
|
|
ScopedMockTransaction transaction(kSimpleGET_Transaction);
|
|
transaction.read_return_code = ERR_INTERNET_DISCONNECTED;
|
|
MockHttpRequest request(transaction);
|
|
request_ = request;
|
|
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_TRUE(CanAddWriters());
|
|
AddTransactionToExistingWriters();
|
|
AddTransactionToExistingWriters();
|
|
|
|
std::vector<std::string> contents(3);
|
|
int rv = ReadNetworkFailure(&contents, ERR_INTERNET_DISCONNECTED);
|
|
|
|
EXPECT_EQ(ERR_INTERNET_DISCONNECTED, rv);
|
|
}
|
|
|
|
// Tests GetLoadState.
|
|
TEST_F(WritersTest, GetLoadState) {
|
|
CreateWritersAddTransaction();
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
EXPECT_EQ(LOAD_STATE_IDLE, writers_->GetLoadState());
|
|
}
|
|
|
|
// Tests truncating logic.
|
|
TEST_F(WritersTest, TruncateEntryFail) {
|
|
CreateWritersAddTransaction();
|
|
|
|
EXPECT_FALSE(writers_->IsEmpty());
|
|
|
|
RemoveFirstTransaction();
|
|
|
|
// Should return false since no content was written to the entry.
|
|
EXPECT_FALSE(ShouldTruncate());
|
|
EXPECT_FALSE(ShouldKeepEntry());
|
|
}
|
|
|
|
// Set network read only.
|
|
TEST_F(WritersTest, StopCachingWithKeepEntry) {
|
|
CreateWritersAddTransaction(HttpCache::PARALLEL_WRITING_NOT_JOIN_RANGE);
|
|
EXPECT_FALSE(writers_->network_read_only());
|
|
|
|
writers_->StopCaching(true /* keep_entry */);
|
|
EXPECT_TRUE(writers_->network_read_only());
|
|
EXPECT_TRUE(ShouldKeepEntry());
|
|
}
|
|
|
|
TEST_F(WritersTest, StopCachingWithNotKeepEntry) {
|
|
CreateWritersAddTransaction(HttpCache::PARALLEL_WRITING_NOT_JOIN_RANGE);
|
|
EXPECT_FALSE(writers_->network_read_only());
|
|
|
|
writers_->StopCaching(false /* keep_entry */);
|
|
EXPECT_TRUE(writers_->network_read_only());
|
|
EXPECT_FALSE(ShouldKeepEntry());
|
|
}
|
|
|
|
// Tests that if content-encoding is set, the entry should not be marked as
|
|
// truncated, since we should not be creating range requests for compressed
|
|
// entries.
|
|
TEST_F(WritersTest, ContentEncodingShouldNotTruncate) {
|
|
CreateWritersAddTransaction(HttpCache::PARALLEL_WRITING_JOIN,
|
|
true /* content_encoding_present */);
|
|
std::string result;
|
|
ReadFewBytes(&result);
|
|
|
|
EXPECT_FALSE(ShouldTruncate());
|
|
EXPECT_FALSE(ShouldKeepEntry());
|
|
}
|
|
|
|
} // namespace net
|