| 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
|
|
|