Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(305)

Unified Diff: net/http/http_cache_unittest.cc

Issue 1230113012: [net] Better StopCaching() handling for HttpCache::Transaction. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Add missing MockTransaction initializers Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: net/http/http_cache_unittest.cc
diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc
index def8cada9f08a65f11fe2977cbe154b513a8ea7a..d214fe808fdc5f7ae55c1dbe67a512c65b669a5b 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -7,9 +7,11 @@
#include <stdint.h>
#include <algorithm>
+#include <list>
#include "base/bind.h"
#include "base/bind_helpers.h"
+#include "base/format_macros.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
@@ -280,6 +282,7 @@ const MockTransaction kFastNoStoreGET_Transaction = {
TEST_MODE_SYNC_NET_START,
&FastTransactionServer::FastNoStoreHandler,
nullptr,
+ nullptr,
0,
0,
OK};
@@ -313,6 +316,8 @@ class RangeTransactionServer {
// X-Require-Mock-Auth -> return 401.
// X-Require-Mock-Auth-Alt -> return 401.
// X-Return-Default-Range -> assume 40-49 was requested.
+ // X-No-Ranges -> Returns "Not a range" if the request is not a valid range
+ // request.
// The -Alt variant doesn't cause the MockNetworkTransaction to
// report that it IsReadyToRestartForAuth().
static void RangeHandler(const HttpRequestInfo* request,
@@ -340,13 +345,23 @@ bool RangeTransactionServer::bad_200_ = false;
// AddHeadersFromString() (_not_ AddHeaderFromString()).
#define EXTRA_HEADER EXTRA_HEADER_LINE "\r\n"
-static const char kExtraHeaderKey[] = "Extra";
+#define RETURN_DEFAULT_RANGE_HEADER "X-Return-Default-Range: 1\r\n"
+
+#define INDICATE_NO_RANGES_HEADER "X-No-Ranges: 1\r\n"
+
+const char kExtraHeaderKey[] = "Extra";
+
+const char kFullRangeData[] =
+ "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 "
+ "rg: 40-49 rg: 50-59 rg: 60-69 rg: 70-79 ";
// Static.
void RangeTransactionServer::RangeHandler(const HttpRequestInfo* request,
std::string* response_status,
std::string* response_headers,
std::string* response_data) {
+ SCOPED_TRACE(testing::Message() << "Request headers: \n"
+ << request->extra_headers.ToString());
if (request->extra_headers.IsEmpty()) {
response_status->assign("HTTP/1.1 416 Requested Range Not Satisfiable");
response_data->clear();
@@ -380,8 +395,17 @@ void RangeTransactionServer::RangeHandler(const HttpRequestInfo* request,
ranges.size() != 1) {
// This is not a byte range request. We return 200.
response_status->assign("HTTP/1.1 200 OK");
- response_headers->assign("Date: Wed, 28 Nov 2007 09:40:09 GMT");
- response_data->assign("Not a range");
+ response_headers->assign("Date: Wed, 28 Nov 2007 09:40:09 GMT\n");
+ if (request->extra_headers.HasHeader("X-No-Ranges")) {
+ response_data->assign("Not a range");
+ return;
+ }
+ response_headers->append(
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ response_data->assign(kFullRangeData);
return;
}
@@ -398,6 +422,7 @@ void RangeTransactionServer::RangeHandler(const HttpRequestInfo* request,
response_data->clear();
return;
}
+ response_status->assign("HTTP/1.1 206 Partial");
EXPECT_TRUE(byte_range.ComputeBounds(80));
int start = static_cast<int>(byte_range.first_byte_position());
@@ -438,28 +463,26 @@ void RangeTransactionServer::RangeHandler(const HttpRequestInfo* request,
}
const MockTransaction kRangeGET_TransactionOK = {
- "http://www.google.com/range",
- "GET",
- base::Time(),
- "Range: bytes = 40-49\r\n" EXTRA_HEADER,
- LOAD_NORMAL,
+ "http://www.google.com/range", "GET", base::Time(),
+ "Range: bytes = 40-49\r\n" EXTRA_HEADER, LOAD_NORMAL,
"HTTP/1.1 206 Partial Content",
"Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
"ETag: \"foo\"\n"
"Accept-Ranges: bytes\n"
"Content-Length: 10\n",
- base::Time(),
- "rg: 40-49 ",
- TEST_MODE_NORMAL,
- &RangeTransactionServer::RangeHandler,
- nullptr,
- 0,
- 0,
- OK};
+ base::Time(), "rg: 40-49 ", TEST_MODE_NORMAL,
+ &RangeTransactionServer::RangeHandler, nullptr, nullptr, 0, 0, OK};
-const char kFullRangeData[] =
- "rg: 00-09 rg: 10-19 rg: 20-29 rg: 30-39 "
- "rg: 40-49 rg: 50-59 rg: 60-69 rg: 70-79 ";
+const MockTransaction kResumableGET_Transaction = {
+ kRangeGET_TransactionOK.url, "GET", base::Time(), EXTRA_HEADER, LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "Date: Sat, 18 Apr 2015 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n",
+ base::Time(), kFullRangeData, TEST_MODE_NORMAL,
+ &RangeTransactionServer::RangeHandler, nullptr, nullptr, 0, 0, OK};
// Verifies the response headers (|response|) match a partial content
// response for the range starting at |start| and ending at |end|.
@@ -2105,6 +2128,7 @@ TEST(HttpCache, GET_DontValidateCache_VaryMismatch) {
RevalidationServer server;
transaction.handler = server.Handler;
transaction.request_headers = "Foo: none\r\n";
+
BoundTestNetLog log;
LoadTimingInfo load_timing_info;
RunTransactionTestAndGetTiming(cache.http_cache(), transaction, log.bound(),
@@ -3903,7 +3927,7 @@ TEST(HttpCache, GET_NoConditionalization) {
// Don't ask for a range. The cache will attempt to use the cached data but
// should discard it as it cannot be validated. A regular request should go
// to the server and a new entry should be created.
- transaction.request_headers = EXTRA_HEADER;
+ transaction.request_headers = INDICATE_NO_RANGES_HEADER EXTRA_HEADER;
transaction.data = "Not a range";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
@@ -4371,7 +4395,8 @@ TEST(HttpCache, GET_206ReturnsSubrangeRange_CachedContent) {
// abort caching, restarting the request.
// The second network request should not be a byte range request so the server
// should return 200 + "Not a range"
- transaction.request_headers = "X-Return-Default-Range:\r\n" EXTRA_HEADER;
+ transaction.request_headers =
+ RETURN_DEFAULT_RANGE_HEADER INDICATE_NO_RANGES_HEADER EXTRA_HEADER;
transaction.data = "Not a range";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
@@ -4763,7 +4788,7 @@ TEST(HttpCache, GET_Previous206_NewContent) {
// server will not modify the response so we'll get the default range... a
// real server will answer with 200.
MockTransaction transaction2(kRangeGET_TransactionOK);
- transaction2.request_headers = EXTRA_HEADER;
+ transaction2.request_headers = INDICATE_NO_RANGES_HEADER EXTRA_HEADER;
transaction2.load_flags |= LOAD_VALIDATE_CACHE;
transaction2.data = "Not a range";
RangeTransactionServer handler;
@@ -5373,7 +5398,8 @@ TEST(HttpCache, RangeGET_FastFlakyServer) {
MockHttpCache cache;
ScopedMockTransaction transaction(kRangeGET_TransactionOK);
- transaction.request_headers = "Range: bytes = 40-\r\n" EXTRA_HEADER;
+ transaction.request_headers =
+ "Range: bytes = 40-\r\n" INDICATE_NO_RANGES_HEADER EXTRA_HEADER;
transaction.test_mode = TEST_MODE_SYNC_NET_START;
transaction.load_flags |= LOAD_VALIDATE_CACHE;
@@ -5948,7 +5974,7 @@ TEST(HttpCache, GET_IncompleteResource2) {
// retry the request without using byte ranges.
std::string headers;
MockTransaction transaction(kRangeGET_TransactionOK);
- transaction.request_headers = EXTRA_HEADER;
+ transaction.request_headers = INDICATE_NO_RANGES_HEADER EXTRA_HEADER;
transaction.data = "Not a range";
RunTransactionTestWithResponse(cache.http_cache(), transaction, &headers);
@@ -6102,7 +6128,7 @@ TEST(HttpCache, GET_IncompleteResource4) {
// Now make a regular request.
std::string headers;
- transaction.request_headers = EXTRA_HEADER;
+ transaction.request_headers = INDICATE_NO_RANGES_HEADER EXTRA_HEADER;
transaction.data = "Not a range";
RangeTransactionServer handler;
handler.set_bad_200(true);
@@ -6257,9 +6283,7 @@ TEST(HttpCache, CachedRedirect) {
ASSERT_EQ(OK, cache.CreateTransaction(&trans));
int rv = trans->Start(&request, callback.callback(), BoundNetLog());
- if (rv == ERR_IO_PENDING)
- rv = callback.WaitForResult();
- ASSERT_EQ(OK, rv);
+ ASSERT_EQ(OK, callback.GetResult(rv));
const HttpResponseInfo* info = trans->GetResponseInfo();
ASSERT_TRUE(info);
@@ -6936,7 +6960,7 @@ TEST(HttpCache, StopCachingTruncatedEntry) {
MockHttpRequest request(kRangeGET_TransactionOK);
request.extra_headers.Clear();
request.extra_headers.AddHeaderFromString(EXTRA_HEADER_LINE);
- AddMockTransaction(&kRangeGET_TransactionOK);
+ ScopedMockTransaction transaction(kRangeGET_TransactionOK);
std::string raw_headers("HTTP/1.1 200 OK\n"
"Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
@@ -6957,21 +6981,712 @@ TEST(HttpCache, StopCachingTruncatedEntry) {
rv = trans->Read(buf.get(), 10, callback.callback());
EXPECT_EQ(callback.GetResult(rv), 10);
- // This is actually going to do nothing.
trans->StopCaching();
// We should be able to keep reading.
rv = trans->Read(buf.get(), 256, callback.callback());
- EXPECT_GT(callback.GetResult(rv), 0);
- rv = trans->Read(buf.get(), 256, callback.callback());
- EXPECT_GT(callback.GetResult(rv), 0);
+ EXPECT_EQ(70, callback.GetResult(rv));
rv = trans->Read(buf.get(), 256, callback.callback());
- EXPECT_EQ(callback.GetResult(rv), 0);
+ EXPECT_EQ(0, callback.GetResult(rv));
}
- // Verify that the disk entry was updated.
- VerifyTruncatedFlag(&cache, kRangeGET_TransactionOK.url, false, 80);
- RemoveMockTransaction(&kRangeGET_TransactionOK);
+ // Verify that the disk entry is still intact and contains the truncated data
+ // from before the request.
+ VerifyTruncatedFlag(&cache, kRangeGET_TransactionOK.url, true, 20);
+}
+
+TEST(HttpCache, StopCachingKeepsSparseEntry) {
+ MockHttpCache cache;
+ TestCompletionCallback callback;
+
+ // Create a sparse entry which contains 10 bytes at offset 20 out of an 80
+ // byte resource.
+ MockTransaction mock_transaction(kRangeGET_TransactionOK);
+ mock_transaction.handler = nullptr;
+ mock_transaction.request_headers = "Range: bytes = 20-29\r\n" EXTRA_HEADER;
+ mock_transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Range: bytes 20-29/80\n"
+ "Content-Length: 10\n";
+ mock_transaction.data = "rg: 20-29 ";
+ AddMockTransaction(&mock_transaction);
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), mock_transaction,
+ &headers);
+
+ // Verify our assumptions. There should be sparse entry here.
+ disk_cache::Entry* cache_entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(mock_transaction.url, &cache_entry));
+ EXPECT_TRUE(cache_entry->CouldBeSparse());
+ cache_entry->Close();
+ RemoveMockTransaction(&mock_transaction);
+
+ cache.ResetCounts();
+
+ // Prepare a request to read the entire contents of the resource. This should
+ // usually hope between network -> cache -> network. But not today.
+ mock_transaction.request_headers = EXTRA_HEADER;
+ mock_transaction.handler = kRangeGET_TransactionOK.handler;
+ mock_transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n";
+ AddMockTransaction(&mock_transaction);
+ MockHttpRequest request(mock_transaction);
+
+ scoped_ptr<HttpTransaction> transaction;
+ ASSERT_EQ(OK, cache.CreateTransaction(&transaction));
+ int rv = transaction->Start(&request, callback.callback(), BoundNetLog());
+ ASSERT_EQ(OK, callback.GetResult(rv));
+
+ const size_t kBufferSize = 256;
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(kBufferSize));
+
+ rv = transaction->Read(buffer.get(), 10, callback.callback());
+ EXPECT_EQ(10, callback.GetResult(rv));
+
+ // This should've caused a network read of 10 bytes.
+ EXPECT_EQ(1, cache.network_layer()->transaction_count());
+
+ // StopCaching() should cause the transaction to switch entirely to the
+ // network for the remainder of the response. Since the existing network
+ // transaction doesn't cover the entirety of the resource, the cache creates a
+ // new network transaction.
+ transaction->StopCaching();
+
+ // Since it's coming from a single network request, we can slurp the remainder
+ // of the resource in one request.
+ rv = transaction->Read(buffer.get(), kBufferSize, callback.callback());
+ EXPECT_EQ(70, callback.GetResult(rv));
+
+ // And we hit EOF.
+ rv = transaction->Read(buffer.get(), kBufferSize, callback.callback());
+ EXPECT_EQ(0, callback.GetResult(rv));
+
+ // Another network transaction is issued for the remainder of the resource
+ // following the StopCaching call.
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+
+ // And the sparse entry is still here.
+ ASSERT_TRUE(cache.OpenBackendEntry(mock_transaction.url, &cache_entry));
+ cache_entry->Close();
+ RemoveMockTransaction(&mock_transaction);
+}
+
+// If StopCaching() is called while serving the entire request out of cache, the
+// transaction is expected to ignore the StopCaching() call and continue serving
+// the remainder of the resource out of the cache.
+TEST(HttpCache, StopCachingKeepsFullEntry) {
+ MockHttpCache cache;
+ TestCompletionCallback callback;
+
+ MockTransaction mock_transaction(kRangeGET_TransactionOK);
+ mock_transaction.handler = nullptr;
+ mock_transaction.request_headers = EXTRA_HEADER;
+ mock_transaction.status = "HTTP/1.1 200 OK";
+ mock_transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Content-Length: 80\n";
+ mock_transaction.data = kFullRangeData;
+ AddMockTransaction(&mock_transaction);
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), mock_transaction,
+ &headers);
+
+ cache.ResetCounts();
+
+ mock_transaction.request_headers = EXTRA_HEADER;
+ mock_transaction.handler = kRangeGET_TransactionOK.handler;
+ mock_transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n";
+ AddMockTransaction(&mock_transaction);
+ MockHttpRequest request(mock_transaction);
+
+ scoped_ptr<HttpTransaction> transaction;
+ ASSERT_EQ(OK, cache.CreateTransaction(&transaction));
+ int rv = transaction->Start(&request, callback.callback(), BoundNetLog());
+ ASSERT_EQ(OK, callback.GetResult(rv));
+
+ const size_t kBufferSize = 256;
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(kBufferSize));
+
+ rv = transaction->Read(buffer.get(), 10, callback.callback());
+ EXPECT_EQ(10, callback.GetResult(rv));
+
+ // This should've caused a cache read of 10 bytes. No new network transactions
+ // are expected.
+ EXPECT_EQ(0, cache.network_layer()->transaction_count());
+
+ // StopCaching() doesn't do anything because the entire resource is cached
+ // already. We proceed with the cache read.
+ transaction->StopCaching();
+
+ // Since it's all coming from a single network request, we can slurp the
+ // remainder of the resource in one request.
+ rv = transaction->Read(buffer.get(), kBufferSize, callback.callback());
+ EXPECT_EQ(70, callback.GetResult(rv));
+
+ // And we hit EOF.
+ rv = transaction->Read(buffer.get(), kBufferSize, callback.callback());
+ EXPECT_EQ(0, callback.GetResult(rv));
+
+ // We should've only seen one network transactions at this point.
+ EXPECT_EQ(0, cache.network_layer()->transaction_count());
+
+ // And the entry is still here.
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+ disk_cache::Entry* cache_entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(mock_transaction.url, &cache_entry));
+ cache_entry->Close();
+
+ RemoveMockTransaction(&mock_transaction);
+}
+
+TEST(HttpCache, StopCachingReleasesCacheLockForNewCacheEntry) {
+ MockHttpCache cache;
+ TestCompletionCallback callback;
+ ScopedMockTransaction mock_transaction(kResumableGET_Transaction);
+ MockHttpRequest mock_request(mock_transaction);
+
+ scoped_ptr<HttpTransaction> first_transaction;
+ ASSERT_EQ(OK, cache.CreateTransaction(&first_transaction));
+
+ int rv = first_transaction->Start(&mock_request, callback.callback(),
+ BoundNetLog());
+ ASSERT_EQ(OK, callback.GetResult(rv));
+
+ const size_t kBufferSize = 1024;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kBufferSize));
+ rv = first_transaction->Read(buf.get(), 10, callback.callback());
+ EXPECT_EQ(10, callback.GetResult(rv));
+
+ first_transaction->StopCaching();
+
+ rv = first_transaction->Read(buf.get(), 10, callback.callback());
+ EXPECT_EQ(10, callback.GetResult(rv));
+
+ // Since the cache entry is no longer locked by |first_transaction|, we can
+ // start another transaction without blocking. The test should time out if
+ // this is not the case (i.e. the Start() call has to wait indefinitely[*]
+ // for the previous transaction to release the lock).
+ //
+ // [*]: The wait isn't really indefinite since the Start() call times out
+ // (currently after 20 seconds) and proceeds without the cache entry. Failure
+ // to release the cahe entry is accounted for by the previous
+ // disk_cache()->GetEntryCount() expectation.
+ scoped_ptr<HttpTransaction> second_transaction;
+ ASSERT_EQ(OK, cache.CreateTransaction(&second_transaction));
+ rv = second_transaction->Start(&mock_request, callback.callback(),
+ BoundNetLog());
+ ASSERT_EQ(OK, callback.GetResult(rv));
+
+ rv = second_transaction->Read(buf.get(), 10, callback.callback());
+ EXPECT_EQ(10, callback.GetResult(rv));
+
+ // First transaction creates a cache entry which the second transaction opens.
+ EXPECT_EQ(1, cache.disk_cache()->create_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+
+ // One network request for each cache transaction.
+ EXPECT_EQ(2, cache.network_layer()->transaction_count());
+
+ first_transaction.reset();
+ second_transaction.reset();
+
+ // The cache entry is still around. It should be truncated and contain the 10
+ // bytes that were written before the first StopCaching() call was made.
+ VerifyTruncatedFlag(&cache, kResumableGET_Transaction.url, true, 10);
+}
+
+TEST(HttpCache, StopCachingReleasesCacheLockForTruncatedEntry) {
+ MockHttpCache cache;
+ TestCompletionCallback callback;
+ ScopedMockTransaction mock_transaction(kResumableGET_Transaction);
+ MockHttpRequest mock_request(kResumableGET_Transaction);
+ std::string raw_headers(
+ "HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "Date: Sat, 18 Apr 2015 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 80\n");
+ CreateTruncatedEntry(raw_headers, &cache);
+ cache.ResetCounts();
+
+ scoped_ptr<HttpTransaction> first_transaction;
+ int rv = cache.CreateTransaction(&first_transaction);
+ ASSERT_EQ(OK, rv);
+ ASSERT_TRUE(first_transaction.get());
+
+ rv = first_transaction->Start(&mock_request, callback.callback(),
+ BoundNetLog());
+ ASSERT_EQ(OK, callback.GetResult(rv));
+
+ first_transaction->StopCaching();
+
+ const int kBufferSize = 1024;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kBufferSize));
+ rv = first_transaction->Read(buf.get(), 10, callback.callback());
+ EXPECT_EQ(10, callback.GetResult(rv));
+
+ // Now that the cache lock is released, another transaction can begin. The
+ // Start() call should complete without delay. The test should timeout if this
+ // is not the case (i.e. the Start() call has to wait indefinitely[*] for the
+ // previous transaction to release the lock).
+ //
+ // [*]: The wait isn't really indefinite since the Start() call times out
+ // (currently after 20 seconds) and proceeds without the cache entry. This
+ // case is accounted for by the network_layer()->transaction_count()
+ // expectation below.
+ scoped_ptr<HttpTransaction> second_transaction;
+ rv = cache.CreateTransaction(&second_transaction);
+ ASSERT_EQ(OK, rv);
+ ASSERT_TRUE(second_transaction);
+ rv = second_transaction->Start(&mock_request, callback.callback(),
+ BoundNetLog());
+ ASSERT_EQ(OK, callback.GetResult(rv));
+
+ rv = second_transaction->Read(buf.get(), 10, callback.callback());
+ EXPECT_EQ(10, callback.GetResult(rv));
+
+ // first_transaction:
+ // 1 open cache entry.
+ // 1 network fetch for validation.
+ // 1 network fetch for switching to the network.
+ // second_transaction:
+ // 1 open cache entry.
+ // 1 network fetch for validation.
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+ EXPECT_EQ(2, cache.disk_cache()->open_count());
+ EXPECT_EQ(3, cache.network_layer()->transaction_count());
+
+ first_transaction.reset();
+ second_transaction.reset();
+ VerifyTruncatedFlag(&cache, kResumableGET_Transaction.url, true, 20);
+}
+
+TEST(HttpCache, StopCachingReleasesCacheLockForSparseEntry) {
+ MockHttpCache cache;
+ TestCompletionCallback callback;
+
+ // Create a sparse entry which contains 10 bytes at offset 20 out of an 80
+ // byte resource.
+ MockTransaction mock_transaction(kRangeGET_TransactionOK);
+ mock_transaction.handler = nullptr;
+ mock_transaction.request_headers = "Range: bytes = 20-29\r\n" EXTRA_HEADER;
+ mock_transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Range: bytes 20-29/80\n"
+ "Content-Length: 10\n";
+ mock_transaction.data = "rg: 20-29 ";
+ AddMockTransaction(&mock_transaction);
+ std::string headers;
+ RunTransactionTestWithResponse(cache.http_cache(), mock_transaction,
+ &headers);
+
+ // Verify our assumptions. There should be sparse entry here.
+ disk_cache::Entry* cache_entry;
+ ASSERT_TRUE(cache.OpenBackendEntry(mock_transaction.url, &cache_entry));
+ EXPECT_TRUE(cache_entry->CouldBeSparse());
+ cache_entry->Close();
+ RemoveMockTransaction(&mock_transaction);
+
+ cache.ResetCounts();
+
+ // Prepare a request to read the entire contents of the resource. This should
+ // usually hope between network -> cache -> network. But not today.
+ mock_transaction.request_headers = EXTRA_HEADER;
+ mock_transaction.handler = kRangeGET_TransactionOK.handler;
+ mock_transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: 10\n";
+ AddMockTransaction(&mock_transaction);
+ MockHttpRequest request(mock_transaction);
+
+ scoped_ptr<HttpTransaction> first_transaction;
+ ASSERT_EQ(OK, cache.CreateTransaction(&first_transaction));
+ int rv =
+ first_transaction->Start(&request, callback.callback(), BoundNetLog());
+ ASSERT_EQ(OK, callback.GetResult(rv));
+
+ const size_t kBufferSize = 256;
+ scoped_refptr<IOBuffer> buffer(new IOBuffer(kBufferSize));
+
+ rv = first_transaction->Read(buffer.get(), 10, callback.callback());
+ EXPECT_EQ(10, callback.GetResult(rv));
+
+ // Create another cache transaction. This one is blocked on the first
+ // transaction's cache lock.
+ scoped_ptr<HttpTransaction> second_transaction;
+ TestCompletionCallback second_callback;
+ ASSERT_EQ(OK, cache.CreateTransaction(&second_transaction));
+ rv = second_transaction->Start(&request, second_callback.callback(),
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, rv);
+
+ // StopCaching() should cause the transaction to switch entirely to the
+ // network for the remainder of the response. Since the existing network
+ // transaction doesn't cover the entirety of the resource, the cache creates a
+ // new network transaction.
+ first_transaction->StopCaching();
+
+ // Since it's coming from a single network request, we can slurp the remaining
+ // 70 bytes of the resource in one request. Let's leave a little bit on the
+ // pipe so that the stream doesn't hit EOF.
+ rv = first_transaction->Read(buffer.get(), 60, callback.callback());
+ EXPECT_EQ(60, callback.GetResult(rv));
+
+ // The second transaction should become unblocked at this point.
+ EXPECT_EQ(OK, second_callback.GetResult(ERR_IO_PENDING));
+
+ // And it can read the entire resource too, but needs to alternate between
+ // cache and network..
+ // 0-9 from cache (due to the first Read() on first_transaction).
+ EXPECT_EQ(10, second_callback.GetResult(second_transaction->Read(
+ buffer.get(), kBufferSize, second_callback.callback())));
+
+ // 10-19 From network:
+ EXPECT_EQ(10, second_callback.GetResult(second_transaction->Read(
+ buffer.get(), kBufferSize, second_callback.callback())));
+
+ // 20-29 From cache (due to test setup):
+ EXPECT_EQ(10, second_callback.GetResult(second_transaction->Read(
+ buffer.get(), kBufferSize, second_callback.callback())));
+
+ // 30-79 From network again:
+ EXPECT_EQ(50, second_callback.GetResult(second_transaction->Read(
+ buffer.get(), kBufferSize, second_callback.callback())));
+ EXPECT_EQ(0, second_callback.GetResult(second_transaction->Read(
+ buffer.get(), kBufferSize, second_callback.callback())));
+
+ // Now drain first_transaction as well.
+ EXPECT_EQ(10, callback.GetResult(first_transaction->Read(
+ buffer.get(), kBufferSize, callback.callback())));
+ EXPECT_EQ(0, callback.GetResult(first_transaction->Read(
+ buffer.get(), kBufferSize, callback.callback())));
+
+ // first_transaction:
+ // 1 open cache entry.
+ // 2 network fetches (0-19 and 10-79).
+ // second_transaction:
+ // 1 open cache entry.
+ // 2 network fetches (10-19 and 30-79).
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+ EXPECT_EQ(4, cache.network_layer()->transaction_count());
+
+ // Even though there are two HttpCache::OpenEntry() calls, there's only one
+ // DiskCache::OpenEntry call because HttpCache already has an active entry.
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+
+ // And the sparse entry is still here.
+ ASSERT_TRUE(cache.OpenBackendEntry(mock_transaction.url, &cache_entry));
+ cache_entry->Close();
+ RemoveMockTransaction(&mock_transaction);
+}
+
+TEST(HttpCache, StopCachingAfterStartForLoadPreferringCacheWithInvalidation) {
+ MockHttpCache cache;
+ TestCompletionCallback callback;
+ MockTransaction transaction(kSimpleGET_Transaction);
+
+ AddMockTransaction(&transaction);
+ RunTransactionTest(cache.http_cache(), transaction);
+ RemoveMockTransaction(&transaction);
+ cache.ResetCounts();
+
+ transaction.load_flags |= LOAD_PREFERRING_CACHE;
+ AddMockTransaction(&transaction);
+
+ scoped_ptr<HttpTransaction> http_transaction;
+
+ int rv = cache.CreateTransaction(&http_transaction);
+ ASSERT_EQ(OK, rv);
+
+ MockHttpRequest request(transaction);
+ rv = http_transaction->Start(&request, callback.callback(), BoundNetLog());
+ ASSERT_EQ(OK, callback.GetResult(rv));
+
+ http_transaction->StopCaching();
+
+ ReadAndVerifyTransaction(http_transaction.get(), transaction);
+ RemoveMockTransaction(&transaction);
+
+ EXPECT_EQ(0, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, cache.disk_cache()->open_count());
+ EXPECT_EQ(0, cache.disk_cache()->create_count());
+}
+
+namespace {
+
+enum class TransactionPhase {
+ BEFORE_FIRST_READ,
+ AFTER_FIRST_READ,
+ AFTER_NETWORK_READ
+};
+
+using CacheInitializer = void (*)(MockHttpCache*);
+using HugeCacheTestConfiguration =
+ std::pair<TransactionPhase, CacheInitializer>;
+
+class HttpCacheHugeResourceTest
+ : public ::testing::TestWithParam<HugeCacheTestConfiguration> {
+ public:
+ static std::list<HugeCacheTestConfiguration> GetTestModes();
+ static std::list<HugeCacheTestConfiguration> kTestModes;
+
+ // CacheInitializer callbacks. These are used to initialize the cache
+ // depending on the test run configuration.
+
+ // Initializes a cache containing a truncated entry containing the first 20
+ // bytes of the reponse body.
+ static void SetupTruncatedCacheEntry(MockHttpCache* cache);
+
+ // Initializes a cache containing a sparse entry. The first 10 bytes are
+ // present in the cache.
+ static void SetupPrefixSparseCacheEntry(MockHttpCache* cache);
+
+ // Initializes a cache containing a sparse entry. The 10 bytes at offset
+ // 99999990 are present in the cache.
+ static void SetupInfixSparseCacheEntry(MockHttpCache* cache);
+
+ protected:
+ static void ExpectByteRangeTransactionHandler(
+ const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data);
+ static int LargeBufferReader(int64 content_length,
+ int64 offset,
+ net::IOBuffer* buf,
+ int buf_len);
+
+ static void SetFlagOnBeforeNetworkStart(bool* started, bool* /* defer */);
+
+ // Size of resource to be tested.
+ static const int64 kTotalSize =
+ 5000000000LL; // Five beeeeeelllliooonn bytes!
+};
+
+const int64 HttpCacheHugeResourceTest::kTotalSize;
+
+// static
+void HttpCacheHugeResourceTest::ExpectByteRangeTransactionHandler(
+ const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ std::string if_range;
+ EXPECT_TRUE(request->extra_headers.GetHeader(
+ net::HttpRequestHeaders::kIfRange, &if_range));
+ EXPECT_EQ("\"foo\"", if_range);
+
+ std::string range_header;
+ EXPECT_TRUE(request->extra_headers.GetHeader(net::HttpRequestHeaders::kRange,
+ &range_header));
+ std::vector<net::HttpByteRange> ranges;
+
+ EXPECT_TRUE(net::HttpUtil::ParseRangeHeader(range_header, &ranges));
+ ASSERT_EQ(1u, ranges.size());
+
+ net::HttpByteRange range = ranges[0];
+ EXPECT_TRUE(range.HasFirstBytePosition());
+ int64 last_byte_position =
+ range.HasLastBytePosition() ? range.last_byte_position() : kTotalSize - 1;
+
+ response_status->assign("HTTP/1.1 206 Partial");
+ response_headers->assign(base::StringPrintf(
+ "Content-Range: bytes %" PRId64 "-%" PRId64 "/%" PRId64
+ "\n"
+ "Content-Length: %" PRId64 "\n",
+ range.first_byte_position(), last_byte_position, kTotalSize,
+ last_byte_position - range.first_byte_position() + 1));
+}
+
+// static
+int HttpCacheHugeResourceTest::LargeBufferReader(int64 content_length,
+ int64 offset,
+ net::IOBuffer* buf,
+ int buf_len) {
+ // This test involves reading multiple gigabytes of data. To make it run in a
+ // reasonable amount of time, we are going to skip filling the buffer with
+ // data. Instead the test relies on verifying that the count of bytes expected
+ // at the end is correct.
+ EXPECT_LT(0, content_length);
+ EXPECT_LE(offset, content_length);
+ int num = std::min(static_cast<int64>(buf_len), content_length - offset);
+ return num;
+}
+
+// static
+void HttpCacheHugeResourceTest::SetFlagOnBeforeNetworkStart(bool* started,
+ bool* /* defer */) {
+ *started = true;
+}
+
+// static
+void HttpCacheHugeResourceTest::SetupTruncatedCacheEntry(MockHttpCache* cache) {
+ ScopedMockTransaction scoped_transaction(kRangeGET_TransactionOK);
+ std::string cached_headers = base::StringPrintf(
+ "HTTP/1.1 200 OK\n"
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Length: %" PRId64 "\n",
+ kTotalSize);
+ CreateTruncatedEntry(cached_headers, cache);
+}
+
+// static
+void HttpCacheHugeResourceTest::SetupPrefixSparseCacheEntry(
+ MockHttpCache* cache) {
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.handler = nullptr;
+ transaction.request_headers = "Range: bytes = 0-9\r\n" EXTRA_HEADER;
+ transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Range: bytes 0-9/5000000000\n"
+ "Content-Length: 10\n";
+ AddMockTransaction(&transaction);
+ std::string headers;
+ RunTransactionTestWithResponse(cache->http_cache(), transaction, &headers);
+ RemoveMockTransaction(&transaction);
+}
+
+// static
+void HttpCacheHugeResourceTest::SetupInfixSparseCacheEntry(
+ MockHttpCache* cache) {
+ MockTransaction transaction(kRangeGET_TransactionOK);
+ transaction.handler = nullptr;
+ transaction.request_headers =
+ "Range: bytes = 99999990-99999999\r\n" EXTRA_HEADER;
+ transaction.response_headers =
+ "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
+ "ETag: \"foo\"\n"
+ "Accept-Ranges: bytes\n"
+ "Content-Range: bytes 99999990-99999999/5000000000\n"
+ "Content-Length: 10\n";
+ AddMockTransaction(&transaction);
+ std::string headers;
+ RunTransactionTestWithResponse(cache->http_cache(), transaction, &headers);
+ RemoveMockTransaction(&transaction);
+}
+
+// static
+std::list<HugeCacheTestConfiguration>
+HttpCacheHugeResourceTest::GetTestModes() {
+ std::list<HugeCacheTestConfiguration> test_modes;
+ const TransactionPhase kTransactionPhases[] = {
+ TransactionPhase::BEFORE_FIRST_READ, TransactionPhase::AFTER_FIRST_READ,
+ TransactionPhase::AFTER_NETWORK_READ};
+ const CacheInitializer kInitializers[] = {&SetupTruncatedCacheEntry,
+ &SetupPrefixSparseCacheEntry,
+ &SetupInfixSparseCacheEntry};
+
+ for (const auto phase : kTransactionPhases)
+ for (const auto initializer : kInitializers)
+ test_modes.push_back(std::make_pair(phase, initializer));
+
+ return test_modes;
+}
+
+// static
+std::list<HugeCacheTestConfiguration> HttpCacheHugeResourceTest::kTestModes =
+ HttpCacheHugeResourceTest::GetTestModes();
+
+INSTANTIATE_TEST_CASE_P(
+ _,
+ HttpCacheHugeResourceTest,
+ ::testing::ValuesIn(HttpCacheHugeResourceTest::kTestModes));
+
+} // namespace
+
+// Test what happens when StopCaching() is called while reading a huge resource
+// fetched via GET. Various combinations of cache state and when StopCaching()
+// is called is controlled by the parameter passed into the test via the
+// INSTANTIATE_TEST_CASE_P invocation above.
+TEST_P(HttpCacheHugeResourceTest,
+ StopCachingFollowedByReadForHugeTruncatedResource) {
+ // This test is going to be repeated for all combinations of TransactionPhase
+ // and CacheInitializers returned by GetTestModes().
+ const TransactionPhase stop_caching_phase = GetParam().first;
+ const CacheInitializer cache_initializer = GetParam().second;
+
+ MockHttpCache cache;
+ (*cache_initializer)(&cache);
+
+ MockTransaction transaction(kSimpleGET_Transaction);
+ transaction.url = kRangeGET_TransactionOK.url;
+ transaction.handler = &ExpectByteRangeTransactionHandler;
+ transaction.read_handler = &LargeBufferReader;
+ ScopedMockTransaction scoped_transaction(transaction);
+
+ MockHttpRequest request(transaction);
+ net::TestCompletionCallback callback;
+ scoped_ptr<net::HttpTransaction> http_transaction;
+ int rv = cache.http_cache()->CreateTransaction(net::DEFAULT_PRIORITY,
+ &http_transaction);
+ ASSERT_EQ(net::OK, rv);
+ ASSERT_TRUE(http_transaction.get());
+
+ bool network_transaction_started = false;
+ if (stop_caching_phase == TransactionPhase::AFTER_NETWORK_READ) {
+ http_transaction->SetBeforeNetworkStartCallback(
+ base::Bind(&SetFlagOnBeforeNetworkStart, &network_transaction_started));
+ }
+
+ rv = http_transaction->Start(&request, callback.callback(),
+ net::BoundNetLog());
+ rv = callback.GetResult(rv);
+ ASSERT_EQ(net::OK, rv);
+
+ if (stop_caching_phase == TransactionPhase::BEFORE_FIRST_READ)
+ http_transaction->StopCaching();
+
+ int64 total_bytes_received = 0;
+
+ EXPECT_EQ(kTotalSize,
+ http_transaction->GetResponseInfo()->headers->GetContentLength());
+ do {
+ // This test simulates reading Gigabytes of data. Buffer size is set to 1MB
+ // to reduce the number of reads and speedup the test.
+ const int kBufferSize = 1024 * 1024;
+ scoped_refptr<net::IOBuffer> buf(new net::IOBuffer(kBufferSize));
+ rv = http_transaction->Read(buf.get(), kBufferSize, callback.callback());
+ rv = callback.GetResult(rv);
+
+ if (stop_caching_phase == TransactionPhase::AFTER_FIRST_READ &&
+ total_bytes_received == 0) {
+ http_transaction->StopCaching();
+ }
+
+ if (rv > 0)
+ total_bytes_received += rv;
+
+ if (network_transaction_started &&
+ stop_caching_phase == TransactionPhase::AFTER_NETWORK_READ) {
+ http_transaction->StopCaching();
+ network_transaction_started = false;
+ }
+ } while (rv > 0);
+
+ // The only verification we are going to do is that the received resource has
+ // the correct size. This is sufficient to verify that the state machine
+ // didn't terminate abruptly due to the StopCaching() call.
+ EXPECT_EQ(kTotalSize, total_bytes_received);
}
// Tests that we detect truncated resources from the net when there is

Powered by Google App Engine
This is Rietveld 408576698