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

Unified Diff: net/http/http_cache_unittest.cc

Issue 455623003: stale-while-revalidate experimental implementation. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase. Created 6 years, 2 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 46dad2760b4e4160c69bc4b86acb0965f69f06e3..a7a04f14d9b54c4e3adc4ea34a389141dc5497d3 100644
--- a/net/http/http_cache_unittest.cc
+++ b/net/http/http_cache_unittest.cc
@@ -10,6 +10,7 @@
#include "base/bind_helpers.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
+#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "net/base/cache_type.h"
@@ -6757,6 +6758,52 @@ TEST(HttpCache, ReceivedBytesRange) {
RemoveMockTransaction(&kRangeGET_TransactionOK);
}
+// Framework for tests of stale-while-revalidate related functionality. With
+// the default settings (age=3601,stale-while-revalidate=7200,max-age=3600) it
+// will trigger the stale-while-revalidate asynchronous revalidation. Setting
+// |age_| to < 3600 will prevent any revalidation, and |age_| > 10800 will cause
+// synchronous revalidation.
+class HttpCacheStaleWhileRevalidateTest : public ::testing::Test {
+ protected:
+ HttpCacheStaleWhileRevalidateTest()
+ : transaction_(kSimpleGET_Transaction),
+ age_(3601),
+ stale_while_revalidate_(7200),
+ validator_("Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT") {
+ cache_.http_cache()->set_use_stale_while_revalidate_for_testing(true);
+ }
+
+ // RunTransactionTest() with the arguments from this fixture.
+ void RunFixtureTransactionTest() {
+ std::string response_headers = base::StringPrintf(
+ "%s\n"
+ "Age: %d\n"
+ "Cache-Control: max-age=3600,stale-while-revalidate=%d\n",
+ validator_.c_str(),
+ age_,
+ stale_while_revalidate_);
+ transaction_.response_headers = response_headers.c_str();
+ RunTransactionTest(cache_.http_cache(), transaction_);
+ transaction_.response_headers = "";
+ }
+
+ // How many times this test has sent requests to the (fake) origin
+ // server. Every test case needs to make at least one request to initialise
+ // the cache.
+ int transaction_count() {
+ return cache_.network_layer()->transaction_count();
+ }
+
+ // How many times an existing cache entry was opened during the test case.
+ int open_count() { return cache_.disk_cache()->open_count(); }
+
+ MockHttpCache cache_;
+ ScopedMockTransaction transaction_;
+ int age_;
+ int stale_while_revalidate_;
+ std::string validator_;
+};
+
static void CheckResourceFreshnessHeader(const net::HttpRequestInfo* request,
std::string* response_status,
std::string* response_headers,
@@ -6768,27 +6815,20 @@ static void CheckResourceFreshnessHeader(const net::HttpRequestInfo* request,
// Verify that the Resource-Freshness header is sent on a revalidation if the
// stale-while-revalidate directive was on the response.
-TEST(HttpCache, ResourceFreshnessHeaderSent) {
- MockHttpCache cache;
-
- ScopedMockTransaction stale_while_revalidate_transaction(
- kSimpleGET_Transaction);
- stale_while_revalidate_transaction.response_headers =
- "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
- "Age: 10801\n"
- "Cache-Control: max-age=3600,stale-while-revalidate=7200\n";
+TEST_F(HttpCacheStaleWhileRevalidateTest, ResourceFreshnessHeaderSent) {
+ age_ = 10801; // Outside the stale-while-revalidate window.
// Write to the cache.
- RunTransactionTest(cache.http_cache(), stale_while_revalidate_transaction);
+ RunFixtureTransactionTest();
- EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, transaction_count());
// Send the request again and check that Resource-Freshness header is added.
- stale_while_revalidate_transaction.handler = CheckResourceFreshnessHeader;
+ transaction_.handler = CheckResourceFreshnessHeader;
- RunTransactionTest(cache.http_cache(), stale_while_revalidate_transaction);
+ RunFixtureTransactionTest();
- EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, transaction_count());
}
static void CheckResourceFreshnessAbsent(const net::HttpRequestInfo* request,
@@ -6800,27 +6840,423 @@ static void CheckResourceFreshnessAbsent(const net::HttpRequestInfo* request,
// Verify that the Resource-Freshness header is not sent when
// stale-while-revalidate is 0.
-TEST(HttpCache, ResourceFreshnessHeaderNotSent) {
- MockHttpCache cache;
-
- ScopedMockTransaction stale_while_revalidate_transaction(
- kSimpleGET_Transaction);
- stale_while_revalidate_transaction.response_headers =
- "Last-Modified: Sat, 18 Apr 2007 01:10:43 GMT\n"
- "Age: 10801\n"
- "Cache-Control: max-age=3600,stale-while-revalidate=0\n";
+TEST_F(HttpCacheStaleWhileRevalidateTest, ResourceFreshnessHeaderNotSent) {
+ age_ = 10801;
+ stale_while_revalidate_ = 0;
// Write to the cache.
- RunTransactionTest(cache.http_cache(), stale_while_revalidate_transaction);
+ RunFixtureTransactionTest();
- EXPECT_EQ(1, cache.network_layer()->transaction_count());
+ EXPECT_EQ(1, transaction_count());
// Send the request again and check that Resource-Freshness header is absent.
- stale_while_revalidate_transaction.handler = CheckResourceFreshnessAbsent;
+ transaction_.handler = CheckResourceFreshnessAbsent;
- RunTransactionTest(cache.http_cache(), stale_while_revalidate_transaction);
+ RunFixtureTransactionTest();
- EXPECT_EQ(2, cache.network_layer()->transaction_count());
+ EXPECT_EQ(2, transaction_count());
+}
+
+// Verify that when stale-while-revalidate applies the response is read from
+// cache.
+TEST_F(HttpCacheStaleWhileRevalidateTest, ReadFromCache) {
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(0, open_count());
+ EXPECT_EQ(1, transaction_count());
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, open_count());
+ EXPECT_EQ(1, transaction_count());
+}
+
+// Verify that when stale-while-revalidate applies an asynchronous request is
+// sent.
+TEST_F(HttpCacheStaleWhileRevalidateTest, AsyncRequestSent) {
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Let the async request execute.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, transaction_count());
+}
+
+// Verify that tearing down the HttpCache with an async revalidation in progress
+// does not break anything (this test is most likely to find problems when run
+// with a memory checker such as AddressSanitizer).
+TEST_F(HttpCacheStaleWhileRevalidateTest, AsyncTearDown) {
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+}
+
+static void CheckIfModifiedSinceHeader(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ std::string value;
+ EXPECT_TRUE(request->extra_headers.GetHeader("If-Modified-Since", &value));
+ EXPECT_EQ("Sat, 18 Apr 2007 01:10:43 GMT", value);
+}
+
+// Verify that the async revalidation contains an If-Modified-Since header.
+TEST_F(HttpCacheStaleWhileRevalidateTest, AsyncRequestIfModifiedSince) {
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ transaction_.handler = CheckIfModifiedSinceHeader;
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+}
+
+static void CheckIfNoneMatchHeader(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ std::string value;
+ EXPECT_TRUE(request->extra_headers.GetHeader("If-None-Match", &value));
+ EXPECT_EQ("\"40a1-1320-4f6adefa22a40\"", value);
+}
+
+// If the response had ETag rather than Last-Modified, then that is used to
+// conditionalise the response.
+TEST_F(HttpCacheStaleWhileRevalidateTest, AsyncRequestIfNoneMatch) {
+ validator_ = "Etag: \"40a1-1320-4f6adefa22a40\"";
+
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ transaction_.handler = CheckIfNoneMatchHeader;
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+}
+
+static void CheckResourceFreshnessHeaderPresent(
+ const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_TRUE(request->extra_headers.HasHeader("Resource-Freshness"));
+}
+
+TEST_F(HttpCacheStaleWhileRevalidateTest, AsyncRequestHasResourceFreshness) {
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ transaction_.handler = CheckResourceFreshnessHeaderPresent;
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+}
+
+// Verify that when age > max-age + stale-while-revalidate stale results are
+// not returned.
+TEST_F(HttpCacheStaleWhileRevalidateTest, NotAppliedIfTooStale) {
+ age_ = 10801;
+
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(0, open_count());
+ EXPECT_EQ(1, transaction_count());
+
+ // Reading back reads from the network.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, open_count());
+ EXPECT_EQ(2, transaction_count());
+}
+
+// HEAD requests should be able to take advantage of stale-while-revalidate.
+TEST_F(HttpCacheStaleWhileRevalidateTest, WorksForHeadMethod) {
+ // Write to the cache. This has to be a GET request; HEAD requests don't
+ // create new cache entries.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(0, open_count());
+ EXPECT_EQ(1, transaction_count());
+
+ // Read back from the cache, and trigger an asynchronous HEAD request.
+ transaction_.method = "HEAD";
+ transaction_.data = "";
+
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, open_count());
+ EXPECT_EQ(1, transaction_count());
+
+ // Let the network request proceed.
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(2, transaction_count());
+}
+
+// POST requests should not use stale-while-revalidate.
+TEST_F(HttpCacheStaleWhileRevalidateTest, NotAppliedToPost) {
+ transaction_ = ScopedMockTransaction(kSimplePOST_Transaction);
+
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(0, open_count());
+ EXPECT_EQ(1, transaction_count());
+
+ // Reading back reads from the network.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(0, open_count());
+ EXPECT_EQ(2, transaction_count());
+}
+
+static void CheckUrlMatches(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_EQ("http://www.google.com/", request->url.spec());
+}
+
+// Async revalidation is issued to the original URL.
+TEST_F(HttpCacheStaleWhileRevalidateTest, AsyncRequestUrlMatches) {
+ transaction_.url = "http://www.google.com/";
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ transaction_.handler = CheckUrlMatches;
+
+ // Let the async request execute and perform the check.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, transaction_count());
+}
+
+class SyncLoadFlagTest : public HttpCacheStaleWhileRevalidateTest,
+ public ::testing::WithParamInterface<int> {};
+
+// Flags which should always cause the request to be synchronous.
+TEST_P(SyncLoadFlagTest, MustBeSynchronous) {
+ transaction_.load_flags |= GetParam();
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Reading back reads from the network.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(2, transaction_count());
+}
+
+INSTANTIATE_TEST_CASE_P(HttpCacheStaleWhileRevalidate,
+ SyncLoadFlagTest,
+ ::testing::Values(net::LOAD_VALIDATE_CACHE,
+ net::LOAD_BYPASS_CACHE,
+ net::LOAD_DISABLE_CACHE));
+
+TEST_F(HttpCacheStaleWhileRevalidateTest,
+ PreferringCacheDoesNotTriggerAsyncRequest) {
+ transaction_.load_flags |= net::LOAD_PREFERRING_CACHE;
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Reading back reads from the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // If there was an async transaction created, it would run now.
+ base::RunLoop().RunUntilIdle();
+
+ // There was no async transaction.
+ EXPECT_EQ(1, transaction_count());
+}
+
+TEST_F(HttpCacheStaleWhileRevalidateTest, NotUsedWhenDisabled) {
+ cache_.http_cache()->set_use_stale_while_revalidate_for_testing(false);
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // A synchronous revalidation is performed.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(2, transaction_count());
+}
+
+TEST_F(HttpCacheStaleWhileRevalidateTest,
+ OnlyFromCacheDoesNotTriggerAsyncRequest) {
+ transaction_.load_flags |= net::LOAD_ONLY_FROM_CACHE;
+ transaction_.return_code = net::ERR_CACHE_MISS;
+
+ // Writing to the cache should fail, because we are avoiding the network.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(0, transaction_count());
+
+ base::RunLoop().RunUntilIdle();
+
+ // Still nothing.
+ EXPECT_EQ(0, transaction_count());
+}
+
+// A certificate error during an asynchronous fetch should cause the next fetch
+// to proceed synchronously.
+// TODO(ricea): In future, only certificate errors which require user
+// interaction should fail the asynchronous revalidation, and they should cause
+// the next revalidation to be synchronous rather than requiring a total
+// refetch. This test will need to be updated appropriately.
+TEST_F(HttpCacheStaleWhileRevalidateTest, CertificateErrorCausesRefetch) {
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Now read back. RunTransactionTestBase() expects to receive the network
+ // error back from the HttpCache::Transaction, but since the cache request
+ // will return OK we need to duplicate some of its implementation here.
+ transaction_.return_code = net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED;
+ net::TestCompletionCallback callback;
+ scoped_ptr<net::HttpTransaction> trans;
+ int rv =
+ cache_.http_cache()->CreateTransaction(net::DEFAULT_PRIORITY, &trans);
+ EXPECT_EQ(net::OK, rv);
+ ASSERT_TRUE(trans.get());
+
+ MockHttpRequest request(transaction_);
+ rv = trans->Start(&request, callback.callback(), net::BoundNetLog());
+ ASSERT_EQ(net::ERR_IO_PENDING, rv);
+ ASSERT_EQ(net::OK, callback.WaitForResult());
+ ReadAndVerifyTransaction(trans.get(), transaction_);
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Allow the asynchronous fetch to run.
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(2, transaction_count());
+
+ // Now run the transaction again. It should run synchronously.
+ transaction_.return_code = net::OK;
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(3, transaction_count());
+}
+
+// Ensure that the response cached by the asynchronous request is not truncated,
+// even if the server is slow.
+TEST_F(HttpCacheStaleWhileRevalidateTest, EntireResponseCached) {
+ transaction_.test_mode = TEST_MODE_SLOW_READ;
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+
+ // Let the async request execute.
+ base::RunLoop().RunUntilIdle();
+
+ // The cache entry should still be complete.
+ transaction_.load_flags = net::LOAD_ONLY_FROM_CACHE;
+ RunFixtureTransactionTest();
+}
+
+// Verify that there are no race conditions in the completely synchronous case.
+TEST_F(HttpCacheStaleWhileRevalidateTest, SynchronousCaseWorks) {
+ transaction_.test_mode = TEST_MODE_SYNC_ALL;
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Let the async request execute.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, transaction_count());
+}
+
+static void CheckLoadFlagsAsyncRevalidation(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ EXPECT_EQ(net::LOAD_ASYNC_REVALIDATION, request->load_flags);
+}
+
+// Check that the load flags on the async request are the same as the load flags
+// on the original request, plus LOAD_ASYNC_REVALIDATION.
+TEST_F(HttpCacheStaleWhileRevalidateTest, LoadFlagsAsyncRevalidation) {
+ transaction_.load_flags = net::LOAD_NORMAL;
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ transaction_.handler = CheckLoadFlagsAsyncRevalidation;
+ // Let the async request execute.
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(2, transaction_count());
+}
+
+static void SimpleMockAuthHandler(const net::HttpRequestInfo* request,
+ std::string* response_status,
+ std::string* response_headers,
+ std::string* response_data) {
+ if (request->extra_headers.HasHeader("X-Require-Mock-Auth") &&
+ !request->extra_headers.HasHeader("Authorization")) {
+ response_status->assign("HTTP/1.1 401 Unauthorized");
+ response_headers->assign("WWW-Authenticate: Basic realm=\"mars\"\n");
+ return;
+ }
+ response_status->assign("HTTP/1.1 200 OK");
+}
+
+TEST_F(HttpCacheStaleWhileRevalidateTest, RestartForAuth) {
+ // Write to the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Now make the transaction require auth.
+ transaction_.request_headers = "X-Require-Mock-Auth: dummy\r\n\r\n";
+ transaction_.handler = SimpleMockAuthHandler;
+
+ // Read back from the cache.
+ RunFixtureTransactionTest();
+
+ EXPECT_EQ(1, transaction_count());
+
+ // Let the async request execute.
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(2, transaction_count());
}
// Tests that we allow multiple simultaneous, non-overlapping transactions to

Powered by Google App Engine
This is Rietveld 408576698