// 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 #include #include #include #include #include "base/bind.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 network_layer, std::unique_ptr 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(), std::make_unique()), 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(disk_entry_, false); (static_cast(disk_entry_))->AddRef(); writers_ = std::make_unique(&test_cache_, entry_.get()); } std::unique_ptr CreateNetworkTransaction() { std::unique_ptr 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 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 transaction = std::make_unique(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 transaction = std::make_unique(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 buf = base::MakeRefCounted(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 buf = base::MakeRefCounted(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& buffer_lengths) { EXPECT_EQ(2u, buffer_lengths.size()); EXPECT_EQ(2u, transactions_.size()); std::vector 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> bufs; for (auto buffer_length : buffer_lengths) bufs.push_back(base::MakeRefCounted(buffer_length)); std::vector 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 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::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 results(transactions_.size()); int rv = 0; bool first_iter = true; do { std::vector> bufs; std::vector callbacks(transactions_.size()); for (size_t i = 0; i < transactions_.size(); i++) { bufs.push_back(base::MakeRefCounted(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 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 buf = base::MakeRefCounted(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(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* results) { int rv = 0; int active_transaction_rv = 0; bool first_iter = true; do { std::vector> bufs; std::vector 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(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* results, Error error) { int rv = 0; std::vector> bufs; std::vector callbacks(results->size()); for (size_t i = 0; i < transactions_.size(); i++) { bufs.push_back(base::MakeRefCounted(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 read_buffer = base::MakeRefCounted(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 writers_; disk_cache::Entry* disk_entry_ = nullptr; std::unique_ptr 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> 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 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 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 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 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 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 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