blob: d0f2e12150fed342030f502d885dbf17048dc4aa [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: jmarantz@google.com (Joshua Marantz)
// Unit-test the lru cache
#include "net/instaweb/http/public/http_cache.h"
#include <cstddef> // for size_t
#include "net/instaweb/http/public/http_value.h"
#include "net/instaweb/http/public/inflating_fetch.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/google_message_handler.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/message_handler.h"
#include "pagespeed/kernel/base/mock_hasher.h"
#include "pagespeed/kernel/base/mock_timer.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/string_writer.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/cache/lru_cache.h"
#include "pagespeed/kernel/cache/write_through_cache.h"
#include "pagespeed/kernel/http/content_type.h"
#include "pagespeed/kernel/http/http_names.h"
#include "pagespeed/kernel/http/response_headers.h"
#include "pagespeed/kernel/util/gzip_inflater.h"
#include "pagespeed/kernel/util/platform.h"
#include "pagespeed/kernel/util/simple_stats.h"
#include "pagespeed/opt/logging/request_timing_info.h"
namespace {
// Set the cache size large enough so nothing gets evicted during this test.
const int kMaxSize = 10000;
const char kStartDate[] = "Sun, 16 Dec 1979 02:27:45 GMT";
const char kUrl[] = "http://www.test.com/";
const char kUrl2[] = "http://www.test.com/2";
const char kUrl3[] = "http://www.test.com/3";
const char kHttpsUrl[] = "https://www.test.com/";
const char kFragment[] = "www.test.com";
const char kFragment2[] = "www.other.com";
} // namespace
namespace net_instaweb {
class HTTPCacheTest : public testing::Test {
protected:
// Helper class for calling Get and Query methods on cache implementations
// that are blocking in nature (e.g. in-memory LRU or blocking file-system).
class Callback : public HTTPCache::Callback {
public:
explicit Callback(const RequestContextPtr& ctx) : HTTPCache::Callback(ctx) {
called_ = false;
cache_valid_ = true;
fresh_ = true;
override_cache_ttl_ms_= -1;
http_value()->Clear();
fallback_http_value()->Clear();
}
virtual void Done(HTTPCache::FindResult result) {
called_ = true;
result_ = result;
}
virtual bool IsCacheValid(const GoogleString& key,
const ResponseHeaders& headers) {
// For unit testing, we are simply stubbing IsCacheValid.
return cache_valid_;
}
virtual bool IsFresh(const ResponseHeaders& headers) {
// For unit testing, we are simply stubbing IsFresh.
return fresh_;
}
virtual int64 OverrideCacheTtlMs(const GoogleString& key) {
return override_cache_ttl_ms_;
}
// Detailed Vary handling is tested in ResponseHeadersTest.
virtual ResponseHeaders::VaryOption RespectVaryOnResources() const {
return ResponseHeaders::kRespectVaryOnResources;
}
bool called_;
HTTPCache::FindResult result_;
bool cache_valid_;
bool fresh_;
int64 override_cache_ttl_ms_;
};
static int64 ParseDate(const char* start_date) {
int64 time_ms;
ResponseHeaders::ParseTime(start_date, &time_ms);
return time_ms;
}
const HTTPCache::FindResult kFoundResult;
const HTTPCache::FindResult kNotFoundResult;
HTTPCacheTest()
: kFoundResult(HTTPCache::kFound, kFetchStatusOK),
kNotFoundResult(HTTPCache::kNotFound, kFetchStatusNotSet),
thread_system_(Platform::CreateThreadSystem()),
simple_stats_(thread_system_.get()),
mock_timer_(thread_system_->NewMutex(), ParseDate(kStartDate)),
lru_cache_(kMaxSize) {
HTTPCache::InitStats(&simple_stats_);
http_cache_.reset(new HTTPCache(&lru_cache_, &mock_timer_, &mock_hasher_,
&simple_stats_));
}
void InitHeaders(ResponseHeaders* headers, const char* cache_control) {
headers->Add("name", "value");
headers->Add("Date", kStartDate);
if (cache_control != NULL) {
headers->Add("Cache-control", cache_control);
}
headers->SetStatusAndReason(HttpStatus::kOK);
headers->ComputeCaching();
}
int GetStat(const char* name) { return simple_stats_.LookupValue(name); }
HTTPCache::FindResult FindWithCallback(
const GoogleString& key, const GoogleString& fragment, HTTPValue* value,
ResponseHeaders* headers, MessageHandler* handler, Callback* callback) {
http_cache_->Find(key, fragment, handler, callback);
EXPECT_TRUE(callback->called_);
if (callback->result_.status == HTTPCache::kFound) {
value->Link(callback->http_value());
}
headers->CopyFrom(*callback->response_headers());
return callback->result_;
}
HTTPCache::FindResult Find(
const GoogleString& key, const GoogleString& fragment, HTTPValue* value,
ResponseHeaders* headers, MessageHandler* handler) {
scoped_ptr<Callback> callback(NewCallback());
return FindWithCallback(
key, fragment, value, headers, handler, callback.get());
}
HTTPCache::FindResult FindAcceptGzip(
const GoogleString& key, const GoogleString& fragment, HTTPValue* value,
ResponseHeaders* headers, MessageHandler* handler) {
scoped_ptr<Callback> callback(NewCallback());
callback->request_context()->SetAcceptsGzip(true);
return FindWithCallback(
key, fragment, value, headers, handler, callback.get());
}
HTTPCache::FindResult Find(
const GoogleString& key, const GoogleString& fragment, HTTPValue* value,
ResponseHeaders* headers, MessageHandler* handler, bool cache_valid) {
scoped_ptr<Callback> callback(NewCallback());
callback->cache_valid_ = cache_valid;
return FindWithCallback(
key, fragment, value, headers, handler, callback.get());
}
Callback* NewCallback() {
return new Callback(RequestContext::NewTestRequestContext(
thread_system_.get()));
}
void Put(const GoogleString& key, const GoogleString& fragment,
ResponseHeaders* headers, const StringPiece& content,
MessageHandler* handler) {
http_cache_->Put(key, fragment, RequestHeaders::Properties(),
ResponseHeaders::kRespectVaryOnResources,
headers, content, handler);
}
void PopulateGzippedEntry(const char* cache_control,
ResponseHeaders* response_headers) {
InitHeaders(response_headers, cache_control);
response_headers->Add(HttpAttributes::kContentType, "text/css");
static const char kCssText[] = ".a {color:blue;} ";
http_cache_->SetCompressionLevel(9);
response_headers->ComputeCaching();
Put(kUrl, kFragment, response_headers, kCssText, &message_handler_);
}
scoped_ptr<ThreadSystem> thread_system_;
SimpleStats simple_stats_;
MockTimer mock_timer_;
MockHasher mock_hasher_;
LRUCache lru_cache_;
scoped_ptr<HTTPCache> http_cache_;
GoogleMessageHandler message_handler_;
private:
DISALLOW_COPY_AND_ASSIGN(HTTPCacheTest);
};
// Simple flow of putting in an item, getting it.
TEST_F(HTTPCacheTest, PutGet) {
simple_stats_.Clear();
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = Find(
kUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kFoundResult, found);
ASSERT_TRUE(meta_data_out.headers_complete());
StringPiece contents;
ASSERT_TRUE(value.ExtractContents(&contents));
ConstStringStarVector values;
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_EQ("content", contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
simple_stats_.Clear();
scoped_ptr<Callback> callback(NewCallback());
// Now advance time 301 seconds and the we should no longer
// be able to fetch this resource out of the cache.
mock_timer_.AdvanceMs(301 * 1000);
found = FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get());
ASSERT_EQ(kNotFoundResult, found);
ASSERT_FALSE(meta_data_out.headers_complete());
EXPECT_EQ(1, GetStat(HTTPCache::kCacheBackendHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheBackendMisses));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheExpirations));
// However, the fallback value should be filled in.
HTTPValue* fallback_value = callback->fallback_http_value();
meta_data_out.Clear();
contents.clear();
EXPECT_FALSE(fallback_value->Empty());
ASSERT_TRUE(fallback_value->ExtractHeaders(&meta_data_out,
&message_handler_));
ASSERT_TRUE(meta_data_out.headers_complete());
ASSERT_TRUE(fallback_value->ExtractContents(&contents));
ASSERT_STREQ("value", meta_data_out.Lookup1("name"));
EXPECT_EQ("content", contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheFallbacks));
// Try again but with the cache invalidated.
simple_stats_.Clear();
scoped_ptr<Callback> callback2(NewCallback());
callback2->cache_valid_ = false;
found = FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback2.get());
ASSERT_EQ(kNotFoundResult, found);
ASSERT_FALSE(meta_data_out.headers_complete());
EXPECT_EQ(1, GetStat(HTTPCache::kCacheBackendHits));
// The fallback is empty since the entry has been invalidated.
fallback_value = callback2->fallback_http_value();
ASSERT_TRUE(fallback_value->Empty());
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
}
TEST_F(HTTPCacheTest, PutGetCompressed) {
// Check to see that when compression is on, data put into the cache is
// properly compressed, and can be retrieved if the callback accepts gzipped
// data.
http_cache_->SetCompressionLevel(9); // Set cache to max compression.
simple_stats_.Clear();
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
meta_data_in.Replace(HttpAttributes::kContentType,
kContentTypeCss.mime_type());
meta_data_in.ComputeCaching();
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = FindAcceptGzip(
kUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kFoundResult, found);
ASSERT_TRUE(meta_data_out.headers_complete());
StringPiece contents;
ASSERT_TRUE(value.ExtractContents(&contents));
ConstStringStarVector values;
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_NE("content", contents);
GoogleString inflated;
StringWriter inflate_writer(&inflated);
GzipInflater::Inflate(contents, GzipInflater::kGzip, &inflate_writer);
EXPECT_STREQ("content", inflated);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
http_cache_->SetCompressionLevel(0); // Return cache to uncompressed.
}
TEST_F(HTTPCacheTest, StaticInflatingFetch) {
// Check to see that when compression is on, data put into the cache is
// properly compressed, and can be retrieved if the callback accepts gzipped
// data.
http_cache_->SetCompressionLevel(9); // Set cache to max compression.
simple_stats_.Clear();
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
meta_data_in.Replace(HttpAttributes::kContentType,
kContentTypeCss.mime_type());
meta_data_in.ComputeCaching();
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = FindAcceptGzip(
kUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kFoundResult, found);
ASSERT_TRUE(meta_data_out.headers_complete());
StringPiece contents;
ASSERT_TRUE(value.ExtractContents(&contents));
ConstStringStarVector values;
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_NE("content", contents);
ResponseHeaders response_headers;
EXPECT_TRUE(value.ExtractHeaders(&response_headers, NULL));
// Check that the InflatingFetch gzip methods work properly when extracting
// data.
HTTPValue ungzipped;
ASSERT_TRUE(InflatingFetch::UnGzipValueIfCompressed(
value, &response_headers, &ungzipped, &message_handler_));
ASSERT_TRUE(ungzipped.ExtractContents(&contents));
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_STREQ("content", contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
http_cache_->SetCompressionLevel(0); // Return cache to uncompressed.
}
TEST_F(HTTPCacheTest, PutGetCompressedJpg) {
// Check to see that when compression is on, data put into the cache is
// properly compressed, and can be retrieved if the callback accepts gzipped
// data. Jpeg data should not be compressed, as gzip doesn't handle non-text
// data well.
http_cache_->SetCompressionLevel(9); // Set cache to max compression.
simple_stats_.Clear();
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
meta_data_in.Replace(HttpAttributes::kContentType,
kContentTypeJpeg.mime_type());
meta_data_in.ComputeCaching();
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = FindAcceptGzip(
kUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kFoundResult, found);
ASSERT_TRUE(meta_data_out.headers_complete());
StringPiece contents;
ASSERT_TRUE(value.ExtractContents(&contents));
ConstStringStarVector values;
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_EQ("content", contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
http_cache_->SetCompressionLevel(0); // Return cache to uncompressed.
}
TEST_F(HTTPCacheTest, PutGetForInvalidUrl) {
simple_stats_.Clear();
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
meta_data_in.Replace(HttpAttributes::kContentType,
kContentTypeHtml.mime_type());
meta_data_in.ComputeCaching();
// The response for the invalid url does not get cached.
Put("blah", kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = Find(
"blah", kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kNotFoundResult, found);
}
TEST_F(HTTPCacheTest, PutGetForHttps) {
simple_stats_.Clear();
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
meta_data_in.Replace(HttpAttributes::kContentType,
kContentTypeHtml.mime_type());
meta_data_in.ComputeCaching();
// Disable caching of html on https.
http_cache_->set_disable_html_caching_on_https(true);
// The html response does not get cached.
Put(kHttpsUrl, kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = Find(
kHttpsUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kNotFoundResult, found);
// However a css file is cached.
meta_data_in.Replace(HttpAttributes::kContentType,
kContentTypeCss.mime_type());
meta_data_in.ComputeCaching();
Put(kHttpsUrl, kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
found = Find(kHttpsUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kFoundResult, found);
ASSERT_TRUE(meta_data_out.headers_complete());
StringPiece contents;
ASSERT_TRUE(value.ExtractContents(&contents));
ConstStringStarVector values;
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_EQ("content", contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
}
TEST_F(HTTPCacheTest, EtagsAddedIfAbsent) {
simple_stats_.Clear();
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = Find(
kUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kFoundResult, found);
ASSERT_TRUE(meta_data_out.headers_complete());
StringPiece contents;
ASSERT_TRUE(value.ExtractContents(&contents));
ConstStringStarVector values;
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_STREQ(HTTPCache::FormatEtag("0"),
meta_data_out.Lookup1(HttpAttributes::kEtag));
EXPECT_EQ("content", contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits)); // The "query" counts as a hit.
}
TEST_F(HTTPCacheTest, EtagsNotAddedIfPresent) {
simple_stats_.Clear();
ResponseHeaders meta_data_in, meta_data_out;
meta_data_in.Add(HttpAttributes::kEtag, "Etag!");
InitHeaders(&meta_data_in, "max-age=300");
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = Find(
kUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kFoundResult, found);
ASSERT_TRUE(meta_data_out.headers_complete());
StringPiece contents;
ASSERT_TRUE(value.ExtractContents(&contents));
ConstStringStarVector values;
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_STREQ("Etag!", meta_data_out.Lookup1(HttpAttributes::kEtag));
EXPECT_EQ("content", contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits)); // The "query" counts as a hit.
}
TEST_F(HTTPCacheTest, CookiesNotCached) {
simple_stats_.Clear();
ResponseHeaders meta_data_in, meta_data_out;
meta_data_in.Add(HttpAttributes::kSetCookie, "cookies!");
meta_data_in.Add(HttpAttributes::kSetCookie2, "more cookies!");
InitHeaders(&meta_data_in, "max-age=300");
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = Find(
kUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kFoundResult, found);
ASSERT_TRUE(meta_data_out.headers_complete());
StringPiece contents;
ASSERT_TRUE(value.ExtractContents(&contents));
ConstStringStarVector values;
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_FALSE(meta_data_out.Lookup(HttpAttributes::kSetCookie, &values));
EXPECT_FALSE(meta_data_out.Lookup(HttpAttributes::kSetCookie2, &values));
EXPECT_EQ("content", contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits)); // The "query" counts as a hit.
}
// Verifies that the cache will 'remember' that a fetch failed according to
// the configured policy.
TEST_F(HTTPCacheTest, RememberFetchFailed) {
ResponseHeaders meta_data_out;
http_cache_->RememberFailure(kUrl, kFragment, kFetchStatusOtherError,
&message_handler_);
HTTPValue value;
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusOtherError),
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
// Now advance time 301 seconds; the cache should allow us to try fetching
// again.
mock_timer_.AdvanceMs(301 * 1000);
EXPECT_EQ(kNotFoundResult,
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
http_cache_->set_failure_caching_ttl_sec(kFetchStatusOtherError, 600);
http_cache_->RememberFailure(kUrl, kFragment, kFetchStatusOtherError,
&message_handler_);
// Now advance time 301 seconds; the cache should remember that the fetch
// failed previously.
mock_timer_.AdvanceMs(301 * 1000);
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusOtherError),
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
}
// Verifies that the cache will 'remember' 'non-cacheable' according to the
// appropriate policy.
TEST_F(HTTPCacheTest, RememberNotCacheable200) {
ResponseHeaders meta_data_out;
http_cache_->RememberFailure(kUrl, kFragment, kFetchStatusUncacheable200,
&message_handler_);
HTTPValue value;
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusUncacheable200),
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
// Now advance time 301 seconds; the cache should allow us to try fetching
// again.
mock_timer_.AdvanceMs(301 * 1000);
EXPECT_EQ(kNotFoundResult,
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
http_cache_->set_failure_caching_ttl_sec(kFetchStatusUncacheable200, 600);
http_cache_->RememberFailure(kUrl, kFragment, kFetchStatusUncacheable200,
&message_handler_);
// Now advance time 301 seconds; the cache should remember that the fetch
// failed previously.
mock_timer_.AdvanceMs(301 * 1000);
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusUncacheable200),
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
}
// Make sure we don't remember 'non-cacheable' once we've put it into
// non-recording of failures mode (but do before that), and that we
// remember successful results even when in SetIgnoreFailurePuts() mode.
TEST_F(HTTPCacheTest, IgnoreFailurePuts) {
http_cache_->RememberFailure(kUrl, kFragment,
kFetchStatusUncacheableError, &message_handler_);
http_cache_->SetIgnoreFailurePuts();
http_cache_->RememberFailure(kUrl2, kFragment,
kFetchStatusUncacheableError, &message_handler_);
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
Put(kUrl3, kFragment, &meta_data_in, "content", &message_handler_);
HTTPValue value_out;
EXPECT_EQ(
HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusUncacheableError),
Find(kUrl, kFragment, &value_out, &meta_data_out, &message_handler_));
EXPECT_EQ(
kNotFoundResult,
Find(kUrl2, kFragment, &value_out, &meta_data_out, &message_handler_));
EXPECT_EQ(
kFoundResult,
Find(kUrl3, kFragment, &value_out, &meta_data_out, &message_handler_));
}
TEST_F(HTTPCacheTest, Uncacheable) {
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, NULL);
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
HTTPValue value;
HTTPCache::FindResult found = Find(
kUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kNotFoundResult, found);
ASSERT_FALSE(meta_data_out.headers_complete());
}
TEST_F(HTTPCacheTest, UncacheablePrivate) {
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "private, max-age=300");
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
HTTPValue value;
HTTPCache::FindResult found = Find(
kUrl, kFragment, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kNotFoundResult, found);
ASSERT_FALSE(meta_data_out.headers_complete());
}
// Unit testing cache invalidation.
TEST_F(HTTPCacheTest, CacheInvalidation) {
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
HTTPValue value;
// Check with cache valid.
EXPECT_EQ(
kFoundResult,
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_, true));
// Check with cache invalidated.
EXPECT_EQ(
kNotFoundResult,
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_, false));
}
TEST_F(HTTPCacheTest, IsFresh) {
const char kDataIn[] = "content";
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
Put(kUrl, kFragment, &meta_data_in, kDataIn, &message_handler_);
HTTPValue value;
scoped_ptr<Callback> callback(NewCallback());
callback->fresh_ = true;
// Check with IsFresh set to true.
EXPECT_EQ(kFoundResult,
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
StringPiece contents;
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_STREQ(kDataIn, contents);
EXPECT_TRUE(callback->fallback_http_value()->Empty());
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
callback.reset(NewCallback());
value.Clear();
callback->fresh_ = false;
// Check with IsFresh set to false.
EXPECT_EQ(kNotFoundResult,
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
EXPECT_TRUE(value.Empty());
EXPECT_TRUE(callback->fallback_http_value()->ExtractContents(&contents));
EXPECT_STREQ(kDataIn, contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheFallbacks));
}
TEST_F(HTTPCacheTest, OverrideCacheTtlMs) {
simple_stats_.Clear();
// First test overriding works for a publicly cacheable response if the
// override TTL is larger than the original one.
const char kDataIn[] = "content";
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
Put(kUrl, kFragment, &meta_data_in, kDataIn, &message_handler_);
HTTPValue value;
scoped_ptr<Callback> callback(NewCallback());
callback->override_cache_ttl_ms_ = 400 * 1000;
EXPECT_EQ(kFoundResult,
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
StringPiece contents;
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_STREQ(kDataIn, contents);
EXPECT_TRUE(callback->fallback_http_value()->Empty());
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_STREQ("max-age=400",
meta_data_out.Lookup1(HttpAttributes::kCacheControl));
// Now, test that overriding has no effect if the override TTL is less than
// the original one.
simple_stats_.Clear();
callback.reset(NewCallback());
value.Clear();
callback->override_cache_ttl_ms_ = 200 * 1000;
EXPECT_EQ(kFoundResult,
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_STREQ(kDataIn, contents);
EXPECT_TRUE(callback->fallback_http_value()->Empty());
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_STREQ("max-age=300",
meta_data_out.Lookup1(HttpAttributes::kCacheControl));
// Now, test that overriding works for Cache-Control: private responses.
simple_stats_.Clear();
callback.reset(NewCallback());
value.Clear();
meta_data_in.Clear();
InitHeaders(&meta_data_in, "private");
Put(kUrl, kFragment, &meta_data_in, kDataIn, &message_handler_);
callback->override_cache_ttl_ms_ = 400 * 1000;
EXPECT_EQ(kFoundResult,
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_STREQ(kDataIn, contents);
EXPECT_TRUE(callback->fallback_http_value()->Empty());
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_STREQ("max-age=400",
meta_data_out.Lookup1(HttpAttributes::kCacheControl));
// Now advance the time by 310 seconds and set override cache TTL to 300
// seconds. The lookup fails.
simple_stats_.Clear();
mock_timer_.AdvanceMs(310 * Timer::kSecondMs);
callback.reset(NewCallback());
value.Clear();
meta_data_in.Clear();
callback->override_cache_ttl_ms_ = 300 * 1000;
EXPECT_EQ(kNotFoundResult,
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheFallbacks));
// Set the override cache TTL to 400 seconds. The lookup succeeds and the
// Cache-Control header is updated.
simple_stats_.Clear();
callback.reset(NewCallback());
value.Clear();
meta_data_in.Clear();
callback->override_cache_ttl_ms_ = 400 * 1000;
EXPECT_EQ(kFoundResult,
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_STREQ(kDataIn, contents);
EXPECT_TRUE(callback->fallback_http_value()->Empty());
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_STREQ("max-age=400",
meta_data_out.Lookup1(HttpAttributes::kCacheControl));
}
TEST_F(HTTPCacheTest, OverrideCacheTtlMsForOriginallyNotCacheable200) {
ResponseHeaders meta_data_out;
http_cache_->RememberFailure(kUrl, kFragment, kFetchStatusUncacheable200,
&message_handler_);
HTTPValue value;
scoped_ptr<Callback> callback(NewCallback());
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusUncacheable200),
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
// Now change the value of override_cache_ttl_ms_. The lookup returns
// kNotFound now.
callback.reset(NewCallback());
value.Clear();
callback->override_cache_ttl_ms_ = 200 * 1000;
EXPECT_EQ(kNotFoundResult,
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
}
TEST_F(HTTPCacheTest, OverrideCacheTtlMsForOriginallyNotCacheableNon200) {
ResponseHeaders meta_data_out;
http_cache_->RememberFailure(kUrl, kFragment, kFetchStatusUncacheableError,
&message_handler_);
HTTPValue value;
scoped_ptr<Callback> callback(NewCallback());
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusUncacheableError),
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
// Now change the value of override_cache_ttl_ms_. The lookup returns
// kNotFound now.
callback.reset(NewCallback());
value.Clear();
callback->override_cache_ttl_ms_ = 200 * 1000;
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusUncacheableError),
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
}
TEST_F(HTTPCacheTest, OverrideCacheTtlMsForOriginallyFetchFailed) {
ResponseHeaders meta_data_out;
http_cache_->RememberFailure(kUrl, kFragment, kFetchStatusOtherError,
&message_handler_);
HTTPValue value;
scoped_ptr<Callback> callback(NewCallback());
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusOtherError),
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
// Now change the value of override_cache_ttl_ms_. The lookup continues to
// return recent failure with kFetchStatusOtherError.
callback.reset(NewCallback());
value.Clear();
callback->override_cache_ttl_ms_ = 200 * 1000;
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusOtherError),
FindWithCallback(kUrl, kFragment, &value, &meta_data_out,
&message_handler_, callback.get()));
}
TEST_F(HTTPCacheTest, FragmentsIndependent) {
HTTPValue value;
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
Put(kUrl, kFragment, &meta_data_in, "content", &message_handler_);
ASSERT_EQ(kFoundResult,
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
ASSERT_EQ(kNotFoundResult,
Find(kUrl, kFragment2, &value, &meta_data_out, &message_handler_));
Put(kUrl, kFragment2, &meta_data_in, "content", &message_handler_);
ASSERT_EQ(kFoundResult,
Find(kUrl, kFragment2, &value, &meta_data_out, &message_handler_));
}
TEST_F(HTTPCacheTest, UpdateVersion) {
HTTPValue value;
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
StringPiece contents;
// Equivalent to pre-versioned caching.
http_cache_->set_version_prefix("");
Put(kUrl, "", &meta_data_in, "v1: No fragment", &message_handler_);
Put(kUrl, kFragment, &meta_data_in, "v1: Fragment", &message_handler_);
EXPECT_EQ(kFoundResult,
Find(kUrl, "", &value, &meta_data_out, &message_handler_));
ASSERT_TRUE(value.ExtractContents(&contents));
EXPECT_STREQ("v1: No fragment", contents);
EXPECT_EQ(kFoundResult,
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
ASSERT_TRUE(value.ExtractContents(&contents));
EXPECT_STREQ("v1: Fragment", contents);
// Setting version invalidates old data.
http_cache_->SetVersion(2);
EXPECT_EQ(kNotFoundResult,
Find(kUrl, "", &value, &meta_data_out, &message_handler_));
EXPECT_EQ(kNotFoundResult,
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
Put(kUrl, "", &meta_data_in, "v2: No fragment", &message_handler_);
Put(kUrl, kFragment, &meta_data_in, "v2: Fragment", &message_handler_);
EXPECT_EQ(kFoundResult,
Find(kUrl, "", &value, &meta_data_out, &message_handler_));
ASSERT_TRUE(value.ExtractContents(&contents));
EXPECT_STREQ("v2: No fragment", contents);
EXPECT_EQ(kFoundResult,
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
ASSERT_TRUE(value.ExtractContents(&contents));
EXPECT_STREQ("v2: Fragment", contents);
// Updating version invalidates old data.
http_cache_->SetVersion(3);
EXPECT_EQ(kNotFoundResult,
Find(kUrl, "", &value, &meta_data_out, &message_handler_));
EXPECT_EQ(kNotFoundResult,
Find(kUrl, kFragment, &value, &meta_data_out, &message_handler_));
}
TEST_F(HTTPCacheTest, NoMutateOnPut) {
ResponseHeaders response_headers;
PopulateGzippedEntry("max-age=300", &response_headers);
// The value that we have in the cache is compressed, but that should
// not cause a mutation in the response headers passed into Put.
EXPECT_FALSE(response_headers.HasValue(
HttpAttributes::kContentEncoding, "gzip"));
}
TEST_F(HTTPCacheTest, DecompressFallbackValue) {
ResponseHeaders response_headers;
PopulateGzippedEntry("max-age=300", &response_headers);
mock_timer_.AdvanceMs(310 * Timer::kSecondMs); // Makes entry stale.
response_headers.Clear();
// If we don't have gzip in the request, it should not be in the fallback
// value.
scoped_ptr<Callback> callback(NewCallback());
HTTPValue value;
EXPECT_EQ(kNotFoundResult, FindWithCallback( // Not found because it's stale.
kUrl, kFragment, &value, &response_headers, &message_handler_,
callback.get()));
EXPECT_TRUE(callback->fallback_http_value()->ExtractHeaders(
&response_headers, &message_handler_));
EXPECT_FALSE(response_headers.HasValue(
HttpAttributes::kContentEncoding, "gzip"));
}
TEST_F(HTTPCacheTest, LeaveFallbackCompressed) {
ResponseHeaders response_headers;
PopulateGzippedEntry("max-age=300", &response_headers);
mock_timer_.AdvanceMs(310 * Timer::kSecondMs); // Makes entry stale.
response_headers.Clear();
// When we enable gzip in the request, though, we will get it because the
// value stored in the cache is gzipped.
RequestContextPtr request_context(RequestContext::NewTestRequestContext(
thread_system_.get()));
request_context->SetAcceptsGzip(true);
Callback callback(request_context);
HTTPValue value;
EXPECT_EQ(kNotFoundResult, FindWithCallback(
kUrl, kFragment, &value, &response_headers, &message_handler_,
&callback));
EXPECT_TRUE(callback.fallback_http_value()->ExtractHeaders(
&response_headers, &message_handler_));
EXPECT_TRUE(response_headers.HasValue(
HttpAttributes::kContentEncoding, "gzip"));
}
class HTTPCacheWriteThroughTest : public HTTPCacheTest {
protected:
// Unlike HTTPCacheTest::Callback this can produce different validity for
// L1/L2 to help testing.
class FakeHttpCacheCallback : public HTTPCache::Callback {
public:
explicit FakeHttpCacheCallback(ThreadSystem* thread_system)
: HTTPCache::Callback(
RequestContext::NewTestRequestContext(thread_system)),
called_(false),
first_call_cache_valid_(true),
first_cache_valid_(true),
second_cache_valid_(true),
first_call_cache_fresh_(true),
first_cache_fresh_(true),
second_cache_fresh_(true) {}
virtual void Done(HTTPCache::FindResult result) {
called_ = true;
result_ = result;
}
virtual bool IsCacheValid(const GoogleString& key,
const ResponseHeaders& headers) {
bool result = first_call_cache_valid_ ?
first_cache_valid_ : second_cache_valid_;
first_call_cache_valid_ = false;
return result;
}
virtual bool IsFresh(const ResponseHeaders& headers) {
bool result = first_call_cache_fresh_ ?
first_cache_fresh_ : second_cache_fresh_;
first_call_cache_fresh_ = false;
return result;
}
virtual ResponseHeaders::VaryOption RespectVaryOnResources() const {
return ResponseHeaders::kRespectVaryOnResources;
}
bool called_;
HTTPCache::FindResult result_;
bool first_call_cache_valid_;
bool first_cache_valid_;
bool second_cache_valid_;
bool first_call_cache_fresh_;
bool first_cache_fresh_;
bool second_cache_fresh_;
};
HTTPCacheWriteThroughTest()
: cache1_(kMaxSize), cache2_(kMaxSize),
write_through_cache_(&cache1_, &cache2_),
key_("http://www.test.com/1"),
key2_("http://www.test.com/2"),
fragment_("www.test.com"),
content_("content"),
cache1_ms_(-1),
cache2_ms_(-1) {
http_cache_.reset(new HTTPCache(&write_through_cache_, &mock_timer_,
&mock_hasher_, &simple_stats_));
http_cache_->set_cache_levels(2);
}
void CheckCachedValueValid() {
HTTPValue value;
ResponseHeaders headers;
HTTPCache::FindResult found = Find(
key_, fragment_, &value, &headers, &message_handler_);
EXPECT_EQ(kFoundResult, found);
EXPECT_TRUE(headers.headers_complete());
StringPiece contents;
EXPECT_TRUE(value.ExtractContents(&contents));
EXPECT_EQ(content_, contents);
EXPECT_STREQ("value", headers.Lookup1("name"));
}
void CheckCachedValueExpired() {
HTTPValue value;
ResponseHeaders headers;
HTTPCache::FindResult found = Find(key_, fragment_, &value, &headers,
&message_handler_);
EXPECT_EQ(kNotFoundResult, found);
EXPECT_FALSE(headers.headers_complete());
}
void ClearStats() {
cache1_.ClearStats();
cache2_.ClearStats();
simple_stats_.Clear();
}
HTTPCache::FindResult Find(const GoogleString& key,
const GoogleString& fragment,
HTTPValue* value,
ResponseHeaders* headers,
MessageHandler* handler) {
FakeHttpCacheCallback callback(thread_system_.get());
http_cache_->Find(key, fragment, handler, &callback);
EXPECT_TRUE(callback.called_);
if (callback.result_.status == HTTPCache::kFound) {
value->Link(callback.http_value());
}
headers->CopyFrom(*callback.response_headers());
callback.request_context()->timing_info().GetHTTPCacheLatencyMs(
&cache1_ms_);
callback.request_context()->timing_info().GetL2HTTPCacheLatencyMs(
&cache2_ms_);
return callback.result_;
}
LRUCache cache1_;
LRUCache cache2_;
WriteThroughCache write_through_cache_;
const GoogleString key_;
const GoogleString key2_;
const GoogleString fragment_;
const GoogleString content_;
int64 cache1_ms_;
int64 cache2_ms_;
};
// Simple flow of putting in an item, getting it.
TEST_F(HTTPCacheWriteThroughTest, PutGet) {
ClearStats();
ResponseHeaders headers_in;
InitHeaders(&headers_in, "max-age=300");
Put(key_, fragment_, &headers_in, content_, &message_handler_);
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(0, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(1, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(1, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
CheckCachedValueValid();
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(1, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(1, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
EXPECT_EQ(0, cache1_ms_);
EXPECT_EQ(-1, cache2_ms_);
// Remove the entry from cache1. We find it in cache2. The value is also now
// inserted into cache1.
cache1_.Clear();
CheckCachedValueValid();
EXPECT_EQ(2, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(1, cache1_.num_misses());
EXPECT_EQ(2, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(1, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(1, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
EXPECT_EQ(0, cache1_ms_);
EXPECT_EQ(0, cache2_ms_);
// Now advance time 301 seconds and the we should no longer
// be able to fetch this resource out of the cache. Note that we check both
// the local and remote cache in this case.
mock_timer_.AdvanceMs(301 * 1000);
CheckCachedValueExpired();
EXPECT_EQ(2, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(2, cache1_.num_hits());
EXPECT_EQ(1, cache1_.num_misses());
EXPECT_EQ(2, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(2, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(1, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
EXPECT_EQ(0, cache1_ms_);
EXPECT_EQ(0, cache2_ms_);
ClearStats();
// Test that fallback_http_value() is set correctly.
FakeHttpCacheCallback callback(thread_system_.get());
http_cache_->Find(key_, fragment_, &message_handler_, &callback);
EXPECT_EQ(kNotFoundResult, callback.result_);
EXPECT_FALSE(callback.fallback_http_value()->Empty());
EXPECT_TRUE(callback.http_value()->Empty());
StringPiece content;
EXPECT_TRUE(callback.fallback_http_value()->ExtractContents(&content));
EXPECT_STREQ(content_, content);
// We find a stale response in the L1 cache, clear it and use the stale
// response in the L2 cache instead.
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(1, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
// Create a temporary HTTPCache with just cache1 and insert a stale response
// into it. We use the fallback from cache2.
HTTPCache temp_l1_cache(&cache1_, &mock_timer_, &mock_hasher_,
&simple_stats_);
// Force caching so that the stale response is inserted.
temp_l1_cache.set_force_caching(true);
temp_l1_cache.Put(key_, fragment_, RequestHeaders::Properties(),
ResponseHeaders::kRespectVaryOnResources,
&headers_in, "new", &message_handler_);
ClearStats();
FakeHttpCacheCallback callback2(thread_system_.get());
http_cache_->Find(key_, fragment_, &message_handler_, &callback2);
EXPECT_EQ(kNotFoundResult, callback2.result_);
EXPECT_FALSE(callback2.fallback_http_value()->Empty());
EXPECT_TRUE(callback2.http_value()->Empty());
StringPiece content2;
EXPECT_TRUE(callback2.fallback_http_value()->ExtractContents(&content2));
EXPECT_STREQ(content_, content2);
// We find a stale response in the L1 cache, clear it and use the stale
// response in the L2 cache instead.
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(1, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
ClearStats();
// Clear cache2. We now use the fallback from cache1.
cache2_.Clear();
FakeHttpCacheCallback callback3(thread_system_.get());
http_cache_->Find(key_, fragment_, &message_handler_, &callback3);
EXPECT_EQ(kNotFoundResult, callback3.result_);
EXPECT_FALSE(callback3.fallback_http_value()->Empty());
EXPECT_TRUE(callback3.http_value()->Empty());
StringPiece content3;
EXPECT_TRUE(callback3.fallback_http_value()->ExtractContents(&content3));
EXPECT_STREQ("new", content3);
// We find a stale response in cache1. Since we don't find anything in cache2,
// we use the stale response from cache1
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(1, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
}
// Check size-limits for the small cache
TEST_F(HTTPCacheWriteThroughTest, SizeLimit) {
ClearStats();
write_through_cache_.set_cache1_limit(183); // See below.
ResponseHeaders headers_in;
InitHeaders(&headers_in, "max-age=300");
// This one will fit. Size:
// Key: v2/www.test.com/http://www.test.com/1 --- 37 bytes.
// Value: 145 bytes
// 145 + 37 = 182.
Put(key_, fragment_, &headers_in, "Name", &message_handler_);
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(1, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(1, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
// This one will not, as the value is a bit bigger and the key has same
// length.
Put(key2_, fragment_, &headers_in, "TooBigForCache1", &message_handler_);
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(2, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(1, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(2, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
}
TEST_F(HTTPCacheWriteThroughTest, PutGetForHttps) {
ClearStats();
ResponseHeaders meta_data_in, meta_data_out;
InitHeaders(&meta_data_in, "max-age=300");
meta_data_in.Replace(HttpAttributes::kContentType,
kContentTypeHtml.mime_type());
meta_data_in.ComputeCaching();
// Disable caching of html on https.
http_cache_->set_disable_html_caching_on_https(true);
// The html response does not get cached.
Put(kHttpsUrl, fragment_, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
HTTPValue value;
HTTPCache::FindResult found = Find(
kHttpsUrl, fragment_, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kNotFoundResult, found);
// However a css file is cached.
meta_data_in.Replace(HttpAttributes::kContentType,
kContentTypeCss.mime_type());
meta_data_in.ComputeCaching();
Put(kHttpsUrl, fragment_, &meta_data_in, "content", &message_handler_);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
found = Find(kHttpsUrl, fragment_, &value, &meta_data_out, &message_handler_);
ASSERT_EQ(kFoundResult, found);
ASSERT_TRUE(meta_data_out.headers_complete());
StringPiece contents;
ASSERT_TRUE(value.ExtractContents(&contents));
ConstStringStarVector values;
ASSERT_TRUE(meta_data_out.Lookup("name", &values));
ASSERT_EQ(static_cast<size_t>(1), values.size());
EXPECT_EQ(GoogleString("value"), *(values[0]));
EXPECT_EQ("content", contents);
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
}
// Verifies that the cache will 'remember' that a fetch should not be
// cached for 5 minutes.
TEST_F(HTTPCacheWriteThroughTest, RememberFetchFailedOrNotCacheable) {
ClearStats();
ResponseHeaders headers_out;
http_cache_->RememberFailure(key_, fragment_, kFetchStatusOtherError,
&message_handler_);
HTTPValue value;
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusOtherError),
Find(key_, fragment_, &value, &headers_out, &message_handler_));
// Now advance time 301 seconds; the cache should allow us to try fetching
// again.
mock_timer_.AdvanceMs(301 * 1000);
EXPECT_EQ(kNotFoundResult,
Find(key_, fragment_, &value, &headers_out, &message_handler_));
}
TEST_F(HTTPCacheWriteThroughTest, RememberFetchDropped) {
ClearStats();
ResponseHeaders headers_out;
http_cache_->RememberFailure(key_, fragment_, kFetchStatusDropped,
&message_handler_);
HTTPValue value;
EXPECT_EQ(HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusDropped),
Find(key_, fragment_, &value, &headers_out, &message_handler_));
// Now advance time 11 seconds; the cache should allow us to try fetching
// again.
mock_timer_.AdvanceMs(11 * Timer::kSecondMs);
EXPECT_EQ(kNotFoundResult,
Find(key_, fragment_, &value, &headers_out, &message_handler_));
}
// Make sure we don't remember 'non-cacheable' once we've put it into
// SetIgnoreFailurePuts() mode (but do before)
TEST_F(HTTPCacheWriteThroughTest, SetIgnoreFailurePuts) {
ClearStats();
http_cache_->RememberFailure(key_, fragment_, kFetchStatusUncacheableError,
&message_handler_);
http_cache_->SetIgnoreFailurePuts();
http_cache_->RememberFailure(key2_, fragment_, kFetchStatusUncacheableError,
&message_handler_);
ResponseHeaders headers_out;
HTTPValue value_out;
EXPECT_EQ(
HTTPCache::FindResult(HTTPCache::kRecentFailure,
kFetchStatusUncacheableError),
Find(key_, fragment_, &value_out, &headers_out, &message_handler_));
EXPECT_EQ(
kNotFoundResult,
Find(key2_, fragment_, &value_out, &headers_out, &message_handler_));
}
TEST_F(HTTPCacheWriteThroughTest, Uncacheable) {
ClearStats();
ResponseHeaders headers_in, headers_out;
InitHeaders(&headers_in, NULL);
Put(key_, fragment_, &headers_in, content_, &message_handler_);
HTTPValue value;
HTTPCache::FindResult found = Find(
key_, fragment_, &value, &headers_out, &message_handler_);
ASSERT_EQ(kNotFoundResult, found);
ASSERT_FALSE(headers_out.headers_complete());
}
TEST_F(HTTPCacheWriteThroughTest, UncacheablePrivate) {
ClearStats();
ResponseHeaders headers_in, headers_out;
InitHeaders(&headers_in, "private, max-age=300");
Put(key_, fragment_, &headers_in, content_, &message_handler_);
HTTPValue value;
HTTPCache::FindResult found = Find(
key_, fragment_, &value, &headers_out, &message_handler_);
ASSERT_EQ(kNotFoundResult, found);
ASSERT_FALSE(headers_out.headers_complete());
}
// Unit testing cache invalidation.
TEST_F(HTTPCacheWriteThroughTest, CacheInvalidation) {
ClearStats();
ResponseHeaders meta_data_in;
InitHeaders(&meta_data_in, "max-age=300");
Put(key_, fragment_, &meta_data_in, content_, &message_handler_);
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(1, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(1, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
// Check with both caches valid...
ClearStats();
FakeHttpCacheCallback callback1(thread_system_.get());
http_cache_->Find(key_, fragment_, &message_handler_, &callback1);
EXPECT_TRUE(callback1.called_);
// ... only goes to cache1_ and hits.
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
EXPECT_EQ(kFoundResult, callback1.result_);
// Check with local cache invalid and remote cache valid...
ClearStats();
FakeHttpCacheCallback callback2(thread_system_.get());
callback2.first_cache_valid_ = false;
http_cache_->Find(key_, fragment_, &message_handler_, &callback2);
EXPECT_TRUE(callback2.called_);
// ... hits both cache1_ (invalidated later by callback2) and cache_2.
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(1, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
// The insert in cache1_ is a reinsert.
EXPECT_EQ(1, cache1_.num_identical_reinserts());
EXPECT_EQ(kFoundResult, callback2.result_);
// Check with both caches invalid...
ClearStats();
FakeHttpCacheCallback callback3(thread_system_.get());
callback3.first_cache_valid_ = false;
callback3.second_cache_valid_ = false;
http_cache_->Find(key_, fragment_, &message_handler_, &callback3);
EXPECT_TRUE(callback3.called_);
// ... hits both cache1_ and cache_2. Both invalidated by callback3. So
// http_cache_ misses.
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(1, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
EXPECT_EQ(kNotFoundResult, callback3.result_);
// Check with local cache valid and remote cache invalid...
ClearStats();
FakeHttpCacheCallback callback4(thread_system_.get());
callback4.second_cache_valid_ = false;
http_cache_->Find(key_, fragment_, &message_handler_, &callback4);
EXPECT_TRUE(callback4.called_);
// ... only goes to cache1_ and hits.
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
EXPECT_EQ(kFoundResult, callback4.result_);
}
// Unit testing cache freshness.
TEST_F(HTTPCacheWriteThroughTest, CacheFreshness) {
ClearStats();
ResponseHeaders meta_data_in;
InitHeaders(&meta_data_in, "max-age=300");
Put(key_, fragment_, &meta_data_in, content_, &message_handler_);
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(0, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(1, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(1, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
// Check with both caches freshe...
ClearStats();
FakeHttpCacheCallback callback1(thread_system_.get());
http_cache_->Find(key_, fragment_, &message_handler_, &callback1);
EXPECT_TRUE(callback1.called_);
// ... only goes to cache1_ and hits.
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
EXPECT_EQ(kFoundResult, callback1.result_);
// Check with local cache not fresh and remote cache fresh...
ClearStats();
FakeHttpCacheCallback callback2(thread_system_.get());
callback2.first_cache_fresh_ = false;
http_cache_->Find(key_, fragment_, &message_handler_, &callback2);
EXPECT_TRUE(callback2.called_);
// ... hits both cache1_ and cache_2.
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(1, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
// The insert in cache1_ is a reinsert.
EXPECT_EQ(1, cache1_.num_identical_reinserts());
EXPECT_EQ(kFoundResult, callback2.result_);
// Check with both caches not fresh...
ClearStats();
FakeHttpCacheCallback callback3(thread_system_.get());
callback3.first_cache_fresh_ = false;
callback3.second_cache_fresh_ = false;
http_cache_->Find(key_, fragment_, &message_handler_, &callback3);
EXPECT_TRUE(callback3.called_);
// ... hits both cache1_ and cache_2. Both aren't fresh. So http_cache_
// misses.
EXPECT_EQ(0, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(1, GetStat(HTTPCache::kCacheFallbacks));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(1, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
EXPECT_EQ(kNotFoundResult, callback3.result_);
EXPECT_FALSE(callback3.fallback_http_value()->Empty());
// Check with local cache fresh and remote cache not fresh...
ClearStats();
FakeHttpCacheCallback callback4(thread_system_.get());
callback4.second_cache_fresh_ = false;
http_cache_->Find(key_, fragment_, &message_handler_, &callback4);
EXPECT_TRUE(callback4.called_);
// ... only goes to cache1_ and hits.
EXPECT_EQ(1, GetStat(HTTPCache::kCacheHits));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheMisses));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheExpirations));
EXPECT_EQ(0, GetStat(HTTPCache::kCacheInserts));
EXPECT_EQ(1, cache1_.num_hits());
EXPECT_EQ(0, cache1_.num_misses());
EXPECT_EQ(0, cache1_.num_inserts());
EXPECT_EQ(0, cache1_.num_deletes());
EXPECT_EQ(0, cache2_.num_hits());
EXPECT_EQ(0, cache2_.num_misses());
EXPECT_EQ(0, cache2_.num_inserts());
EXPECT_EQ(0, cache2_.num_deletes());
EXPECT_EQ(kFoundResult, callback4.result_);
}
} // namespace net_instaweb