| /* |
| * Copyright 2011 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 RewriteContext class. This is made simplest by |
| // setting up some dummy rewriters in our test framework. |
| |
| #include "net/instaweb/rewriter/public/rewrite_context.h" |
| |
| #include "net/instaweb/http/public/async_fetch.h" |
| #include "net/instaweb/http/public/async_fetch_with_lock.h" |
| #include "net/instaweb/http/public/counting_url_async_fetcher.h" |
| #include "net/instaweb/http/public/http_cache_failure.h" |
| #include "net/instaweb/http/public/logging_proto_impl.h" |
| #include "net/instaweb/http/public/mock_url_fetcher.h" |
| #include "net/instaweb/http/public/rate_controller.h" |
| #include "net/instaweb/http/public/request_context.h" |
| #include "net/instaweb/http/public/wait_url_async_fetcher.h" |
| #include "net/instaweb/rewriter/public/common_filter.h" |
| #include "net/instaweb/rewriter/public/domain_lawyer.h" |
| #include "net/instaweb/rewriter/public/fake_filter.h" |
| #include "net/instaweb/rewriter/public/file_load_policy.h" |
| #include "net/instaweb/rewriter/public/output_resource_kind.h" |
| #include "net/instaweb/rewriter/public/resource.h" // for ResourcePtr, etc |
| #include "net/instaweb/rewriter/public/resource_slot.h" |
| #include "net/instaweb/rewriter/public/rewrite_context_test_base.h" |
| #include "net/instaweb/rewriter/public/rewrite_driver.h" |
| #include "net/instaweb/rewriter/public/rewrite_filter.h" |
| #include "net/instaweb/rewriter/public/rewrite_options.h" |
| #include "net/instaweb/rewriter/public/rewrite_stats.h" |
| #include "net/instaweb/rewriter/public/rewrite_test_base.h" |
| #include "net/instaweb/rewriter/public/server_context.h" |
| #include "net/instaweb/rewriter/public/simple_text_filter.h" |
| #include "net/instaweb/rewriter/public/single_rewrite_context.h" |
| #include "net/instaweb/rewriter/public/test_rewrite_driver_factory.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/charset_util.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/mem_file_system.h" |
| #include "pagespeed/kernel/base/mock_message_handler.h" |
| #include "pagespeed/kernel/base/named_lock_manager.h" |
| #include "pagespeed/kernel/base/named_lock_tester.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/statistics.h" |
| #include "pagespeed/kernel/base/stl_util.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.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/html/html_element.h" |
| #include "pagespeed/kernel/html/html_name.h" |
| #include "pagespeed/kernel/html/html_parse_test_base.h" |
| #include "pagespeed/kernel/http/content_type.h" |
| #include "pagespeed/kernel/http/http_names.h" // for Code::kOK |
| #include "pagespeed/kernel/http/request_headers.h" |
| #include "pagespeed/kernel/http/response_headers.h" |
| #include "pagespeed/kernel/http/semantic_type.h" |
| #include "pagespeed/kernel/http/user_agent_matcher_test_base.h" |
| #include "pagespeed/kernel/thread/queued_worker_pool.h" |
| #include "pagespeed/kernel/thread/worker_test_base.h" |
| |
| namespace net_instaweb { |
| |
| namespace { |
| |
| // This value needs to be bigger than rewrite driver timeout; |
| // and it's useful while debugging for it to not be the driver |
| // timeout's multiple (so one can easily tell its occurrences |
| // from repetitions of the driver's timeout). |
| const int64 kRewriteDelayMs = 47; |
| |
| } // namespace |
| |
| class RewriteContextTest : public RewriteContextTestBase { |
| protected: |
| RewriteContextTest() { |
| fetch_failures_ = statistics()->GetVariable( |
| RewriteStats::kNumResourceFetchFailures); |
| fetch_successes_ = statistics()->GetVariable( |
| RewriteStats::kNumResourceFetchSuccesses); |
| } |
| |
| void InitTrimFiltersSync(OutputResourceKind kind) { |
| rewrite_driver()->AppendRewriteFilter( |
| new TrimWhitespaceSyncFilter(kind, rewrite_driver())); |
| rewrite_driver()->AddFilters(); |
| |
| other_rewrite_driver()->AppendRewriteFilter( |
| new TrimWhitespaceSyncFilter(kind, rewrite_driver())); |
| other_rewrite_driver()->AddFilters(); |
| |
| EnableDebug(); |
| } |
| |
| void InitTwoFilters(OutputResourceKind kind) { |
| InitUpperFilter(kind, rewrite_driver()); |
| InitUpperFilter(kind, other_rewrite_driver()); |
| InitTrimFilters(kind); |
| } |
| |
| void TrimOnTheFlyStart(GoogleString* input_html, GoogleString* output_html) { |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| // The first rewrite was successful because we got an 'instant' url |
| // fetch, not because we did any cache lookups. |
| *input_html = CssLinkHref("a.css"); |
| *output_html = |
| StrCat(CssLinkHref(Encode("", "tw", "0", "a.css", "css")), |
| DebugMessage("a.css")); |
| ValidateExpected("trimmable", *input_html, *output_html); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); // Metadata + input-resource. |
| // We expect 2 inserts because it's an kOnTheFlyResource. |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // Metadata + input-resource. |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| EXPECT_EQ(1, fetch_successes_->Get()); |
| EXPECT_EQ(0, fetch_failures_->Get()); |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(1, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(1, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_rewrites_completed()); |
| ClearStats(); |
| |
| // The second time we request this URL, we should find no additional |
| // cache inserts or fetches. The rewrite should complete using a |
| // single cache hit for the metadata. No cache misses will occur. |
| ValidateExpected("trimmable", *input_html, *output_html); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_rewrites_completed()); |
| EXPECT_EQ(0, fetch_successes_->Get()); // no more fetches. |
| EXPECT_EQ(0, fetch_failures_->Get()); |
| ClearStats(); |
| } |
| |
| int RewriteAndCountUnrewrittenCss(const GoogleString& id, |
| const GoogleString& input_html) { |
| Parse(id, input_html); |
| CssLink::Vector css_links; |
| GoogleString rewritten_html = output_buffer_; |
| CollectCssLinks("collecting_links", rewritten_html, &css_links); |
| int num_unrewritten_css = 0; |
| for (int i = 0, n = css_links.size(); i < n; ++i) { |
| if (css_links[i]->url_.find(".pagespeed.") == GoogleString::npos) { |
| ++num_unrewritten_css; |
| } |
| } |
| return num_unrewritten_css; |
| } |
| |
| NamedLock* MakeInputLock(const GoogleString& name) { |
| return AsyncFetchWithLock::MakeInputLock(name, |
| server_context()->lock_hasher(), |
| server_context()->lock_manager()); |
| } |
| |
| void ResetUserAgent(StringPiece user_agent) { |
| ClearRewriteDriver(); |
| SetCurrentUserAgent(user_agent); |
| SetDriverRequestHeaders(); |
| } |
| |
| Variable* fetch_failures_; |
| Variable* fetch_successes_; |
| }; |
| |
| TEST_F(RewriteContextTest, TrimOnTheFlyOptimizable) { |
| GoogleString input_html, output_html; |
| TrimOnTheFlyStart(&input_html, &output_html); |
| |
| // The third time we request this URL, we've advanced time so that the origin |
| // resource TTL has expired. The data will be re-fetched, and the Date |
| // corrected. See url_input_resource.cc, AddToCache(). The http cache will |
| // miss, but we'll re-insert. We won't need to do any more rewrites because |
| // the data did not actually change. |
| AdvanceTimeMs(2 * kOriginTtlMs); |
| ValidateExpected("trimmable", input_html, output_html); |
| EXPECT_EQ(2, lru_cache()->num_hits()); // 1 expired hit, 1 valid hit. |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // re-inserts after expiration. |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, http_cache()->cache_expirations()->Get()); |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_misses()); |
| EXPECT_EQ(1, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(1, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_rewrites_completed()); |
| EXPECT_EQ(1, fetch_successes_->Get()); // Must freshen. |
| EXPECT_EQ(0, fetch_failures_->Get()); |
| ClearStats(); |
| |
| // The fourth time we request this URL, the cache is in good shape despite |
| // the expired date header from the origin. |
| |
| ValidateExpected("trimmable", input_html, output_html); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_rewrites_completed()); |
| EXPECT_EQ(0, fetch_successes_->Get()); // no more fetches. |
| EXPECT_EQ(0, fetch_failures_->Get()); |
| ClearStats(); |
| |
| // Induce a metadata cache flush by tweaking the options in way that |
| // happens to be irrelevant for the filter applied. We will |
| // successfully rewrite, but we will not need to re-fetch. |
| options()->ClearSignatureForTesting(); |
| options()->EnableFilter(RewriteOptions::kInlineImages); |
| options()->ComputeSignature(); |
| ValidateExpected("trimmable_flushed_metadata", input_html, output_html); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // resource |
| EXPECT_EQ(1, lru_cache()->num_misses()); // metadata |
| EXPECT_EQ(1, lru_cache()->num_inserts()); // metadata |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(1, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(1, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_rewrites_completed()); |
| ClearStats(); |
| EXPECT_EQ(0, fetch_successes_->Get()); // no more fetches. |
| EXPECT_EQ(0, fetch_failures_->Get()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimOnTheFlyWithVaryCookie) { |
| InitTrimFilters(kOnTheFlyResource); |
| ResponseHeaders response_headers; |
| SetDefaultLongCacheHeaders(&kContentTypeCss, &response_headers); |
| response_headers.Add(HttpAttributes::kVary, HttpAttributes::kCookie); |
| SetFetchResponse(AbsolutifyUrl("a.css"), response_headers, " a "); |
| |
| // We cannot rewrite resources with Vary:Cookie in the response, |
| // even if there was no cookie in the request. It is conceivable to |
| // implement a policy where Vary:Cookie is tolerated in the response |
| // as long as there are no cookies in the request. We would have to |
| // ensure that we emitted the Vary:Cookie when serving the response |
| // for the benefit of any other proxy caches. The real challenge is |
| // that the original domain of the resources might not be the same |
| // as the domain of the HTML, so when serving HTML we would not know |
| // whether the client had clear cookies for the resource fetch. So |
| // we could only do that if we knew the mapped resource domain was |
| // cookieless, or the domain was the same as the HTML domain. |
| // |
| // Since the number of resources this affects on the internet is |
| // very small -- less than 1% we will not be trying to tackle If we |
| // do, this test will have to change to ValidateExpected against |
| // CssLinkHref(Encode("", "tw", "0", "a.css", "css"), and we'd have |
| // to also test that we didn't do the rewrite when there were |
| // cookies on the HTML request. |
| GoogleString input_html = CssLinkHref("a.css"); |
| ValidateNoChanges("vary_cookie", input_html); |
| } |
| |
| TEST_F(RewriteContextTest, UnhealthyCacheNoHtmlRewrites) { |
| lru_cache()->set_is_healthy(false); |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| // We expect no changes in the HTML because the system gives up without |
| // a healthy cache. No cache lookups or fetches are attempted in this |
| // flow, though if we need to handle a request for a .pagespeed. url |
| // then we'll have to do fetches for that. |
| GoogleString input_html(CssLinkHref("a.css")); |
| ValidateNoChanges("trimmable", input_html); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_hits()); |
| } |
| |
| |
| TEST_F(RewriteContextTest, TrimOnTheFlyOptimizableCacheInvalidation) { |
| GoogleString input_html, output_html; |
| TrimOnTheFlyStart(&input_html, &output_html); |
| |
| // The third time we invalidate the cache and then request the URL. |
| SetCacheInvalidationTimestamp(); |
| ValidateExpected("trimmable", input_html, output_html); |
| |
| rewrite_driver()->WaitForShutDown(); |
| // Setting the cache invalidation timestamp causes the partition key to change |
| // and hence we get a cache miss (and insert) on the metadata. The HTTPCache |
| // is also invalidated and hence we have a fetch + insert of a.css. |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, CacheInvalidatingOneOfTwoCssFiles) { |
| EnableCachePurge(); |
| GoogleString input_html, output_html; |
| TrimOnTheFlyStart(&input_html, &output_html); |
| |
| // Also include 'b.css' to input & output HTML. |
| StrAppend(&input_html, CssLinkHref("b.css")); |
| StrAppend(&output_html, CssLinkHref("b.css")); // 'b.css' is not optimizable. |
| |
| // Invalidate the whole cache & re-run, generating metadata cache entries for |
| // a.css and b.css. |
| SetCacheInvalidationTimestamp(); |
| ValidateExpected("trimmable", input_html, output_html); |
| ClearStats(); |
| |
| // Wipe out a.css, but b.css's metadata stays intact. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("a.css"), |
| false /* ignores_metadata_and_pcache */); |
| ValidateExpected("trimmable", input_html, output_html); |
| |
| // The invalidation of a.css does not actually change the cache key or remove |
| // it from the cache; the metadata is invalidated after the cache hit. Then |
| // we must re-fetch a.css, which results in an cache hit HTTP cache hit. |
| // There are no physical cache misses, but we do re-insert the same value |
| // in the lru-cache after re-fetching a.css and seeing it didn't change. |
| EXPECT_EQ(3, lru_cache()->num_hits()); // a.css, b.css, re-fetch of a.css. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); // a.css was invalidated. |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, lru_cache()->num_identical_reinserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Zooming into the metadata cache, we see a miss at this level due to |
| // the invalidation record we wrote. |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(1, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(1, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(2, metadata_cache_info().num_rewrites_completed()); |
| } |
| |
| TEST_F(RewriteContextTest, |
| TrimOnTheFlyOptimizableThisUrlCacheInvalidationIgnoringMetadataCache) { |
| EnableCachePurge(); |
| GoogleString input_html, output_html; |
| TrimOnTheFlyStart(&input_html, &output_html); |
| |
| // The third time we do a 'strict' invalidation of cache for some other URL |
| // and then request the URL. This means we do not invalidate the metadata, |
| // nor the HTTP cache entry for 'a.css'. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("foo.bar"), |
| true /* ignores_metadata_and_pcache */); |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| // We get a cache hit on the metadata. |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The fourth time we do a 'strict' invalidation of cache for 'a.css' and then |
| // request the URL. This means we do not invalidate the metadata, but HTTP |
| // cache entry for 'a.css' is invalidated. |
| // Note: Strict invalidation does not make sense for resources, since one |
| // almost always wants to invalidate metadata for resources. This test is for |
| // completeness. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("a.css"), |
| true /* ignores_metadata_and_pcache */); |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| // We get a cache hit on the metadata. |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimOnTheFlyOptimizableThisUrlCacheInvalidation) { |
| EnableCachePurge(); |
| GoogleString input_html, output_html; |
| TrimOnTheFlyStart(&input_html, &output_html); |
| |
| // The third time we do a 'strict' invalidation of cache for some other URL |
| // and then request the URL. This means we do not invalidate the metadata, |
| // nor the HTTP cache entry for 'a.css'. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("foo.bar"), true); |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| // We get a cache hit on the metadata. |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The fourth time we do a 'complete' invalidation of cache for 'a.css' and |
| // then request the URL. This means in addition to invalidating the HTTP |
| // cache entry for 'a.css', the metadata for that item is also invalidated, |
| // though the metadata for 'b.css' is not disturbed. |
| ResponseHeaders default_css_header; |
| SetDefaultLongCacheHeaders(&kContentTypeCss, &default_css_header); |
| int64 now_ms = http_cache()->timer()->NowMs(); |
| default_css_header.SetDateAndCaching(now_ms, kOriginTtlMs); |
| default_css_header.ComputeCaching(); |
| SetFetchResponse(StrCat(kTestDomain, "a.css"), default_css_header, " new_a "); |
| AdvanceTimeMs(1); |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("a.css"), false); |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| // The above invalidation did not cause the partition key to change, and so |
| // we get an LRU cache hit. However, the InputInfo is invalid because we |
| // purged the cache, so we'll do a fetch, rewrite, and -reinsert. |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // metadata & http |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimOnTheFlyOptimizableUrlCacheInvalidation) { |
| EnableCachePurge(); |
| GoogleString input_html, output_html; |
| TrimOnTheFlyStart(&input_html, &output_html); |
| |
| // The third time we do a 'complete' invalidation of cache for some other URL |
| // and then request the URL. This means all metadata is invalidated, but the |
| // HTTP cache entry for 'a.css' is not. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("foo.bar*"), false); |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| // The above invalidation causes the partition key to change and hence |
| // we get a cache miss (and insert) on the metadata. The HTTPCache is not |
| // invalidated and hence we get a hit there (and not fetch). |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| EXPECT_EQ(1, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimOnTheFlyNonOptimizable) { |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| // In this case, the resource is not optimizable. The cache pattern is |
| // exactly the same as when the resource was optimizable. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // We should have cached the failed rewrite, no misses, fetches, or inserts. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimOnTheFlyNonOptimizableCacheInvalidation) { |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| // In this case, the resource is not optimizable. The cache pattern is |
| // exactly the same as when the resource was optimizable. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // We should have cached the failed rewrite, no misses, fetches, or inserts. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The third time we invalidate the cache and then request the URL. |
| SetCacheInvalidationTimestamp(); |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| // Setting the cache invalidation timestamp causes the partition key to change |
| // and hence we get a cache miss (and insert) on the metadata. The HTTPCache |
| // is also invalidated and hence we have a fetch, and re-insert of b.css |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, |
| TrimOnTheFlyNonOptimizableThisStrictUrlCacheInvalidation) { |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| // In this case, the resource is not optimizable. The cache pattern is |
| // exactly the same as when the resource was optimizable. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // We should have cached the failed rewrite, no misses, fetches, or inserts. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The third time we do a 'strict' invalidation of the cache for some URL |
| // other than 'b.css' and then request the URL. This means that metdata (and |
| // in fact also HTTP cache for 'b.css') are not invalidated. |
| // Note: This is realistic since strict invalidation is what makes sense for |
| // html. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("foo.bar"), true); |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The fourth time we do a 'strict' invalidation of the caches for 'b.css' and |
| // then request the URL. This means we do not invalidate the metadata (but |
| // HTTP cache is invalidated) and hence we get the cached failed rewrite from |
| // metadata cache. |
| // Note: Strict invalidation does not make sense for resources, since one |
| // almost always wants to invalidate metadata for resources. This test is for |
| // completeness. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("b.css"), true); |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, |
| TrimOnTheFlyNonOptimizableThisRefUrlCacheInvalidation) { |
| EnableCachePurge(); |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| // In this case, the resource is not optimizable. The cache pattern is |
| // exactly the same as when the resource was optimizable. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // We should have cached the failed rewrite, no misses, fetches, or inserts. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The third time we do a 'strict' invalidation of the cache for some URL |
| // other than 'b.css' and then request the URL. This means that metdata (and |
| // in fact also HTTP cache for 'b.css') are not invalidated. |
| // Note: This is realistic since strict invalidation is what makes sense for |
| // html. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("foo.bar"), true); |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The fourth time we invalidate the caches for 'b.css' and all metadata and |
| // then request the URL. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("b.css"), false); |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| // The above invalidation does not cause the partition key to |
| // change, so we get an LRU cache hit, but we detect that it's |
| // invalid and then re-insert the metadata. The HTTPCache is also |
| // invalidated and hence we have a fetch and new insert of b.css |
| EXPECT_EQ(2, lru_cache()->num_hits()); // metadata (invalid) + b.css. |
| EXPECT_EQ(1, metadata_cache_info().num_misses()); |
| EXPECT_EQ(1, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(1, metadata_cache_info().num_rewrites_completed()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimOnTheFlyNonOptimizableUrlCacheInvalidation) { |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| // In this case, the resource is not optimizable. The cache pattern is |
| // exactly the same as when the resource was optimizable. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // We should have cached the failed rewrite, no misses, fetches, or inserts. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The third time we do a 'non-strict' (includes metadata) invalidation of |
| // the cache for some URL other than 'b.css', invalidating just the |
| // metadata for foo.bar, which has no effect. |
| SetCacheInvalidationTimestampForUrl(AbsolutifyUrl("foo.bar"), false); |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| // Since enable_cache_purge is not true, the above invalidation results in a |
| // signature change for metadata cache key. Hence metadata is invalidated. |
| EXPECT_EQ(1, lru_cache()->num_hits()); // http cache |
| EXPECT_EQ(1, lru_cache()->num_misses()); // metadata |
| EXPECT_EQ(1, lru_cache()->num_inserts()); // metadata |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // In this variant, we use the same whitespace trimmer, but we pretend that this |
| // is an expensive operation, so we want to cache the output resource. This |
| // means we will do an extra cache insert on the first iteration for each input. |
| TEST_F(RewriteContextTest, TrimRewrittenOptimizable) { |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| // The first rewrite was successful because we got an 'instant' url |
| // fetch, not because we did any cache lookups. We'll have 2 cache |
| // misses: one for the OutputPartitions, one for the fetch. We |
| // should need three items in the cache: the element, the resource |
| // mapping (OutputPartitions) and the output resource. |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); // 3 cause it's kRewrittenResource |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The second cache time we request this URL, we should find no additional |
| // cache inserts or fetches. The rewrite should complete using a single |
| // cache hit for the metadata (or output?). No cache misses will occur. |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimRewrittenNonOptimizable) { |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| // In this case, the resource is not optimizable. The cache pattern is |
| // exactly the same as when the resource was on-the-fly and optimizable. |
| // We'll cache the successfully fetched resource, and the OutputPartitions |
| // which indicates the unsuccessful optimization. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // We should have cached the failed rewrite, no misses, fetches, or inserts. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimRepeatedOptimizable) { |
| // Make sure two instances of the same link are handled properly, |
| // when optimization succeeds. |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| ValidateExpected( |
| "trimmable2", StrCat(CssLinkHref("a.css"), CssLinkHref("a.css")), |
| StrCat(CssLinkHref(Encode("", "tw", "0", "a.css", "css")), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css")))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimRepeatedOptimizableDelayed) { |
| // Make sure two instances of the same link are handled properly, |
| // when optimization succeeds --- but fetches are slow. |
| SetupWaitFetcher(); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| // First time nothing happens by deadline. |
| ValidateNoChanges("trimable2_notyet", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("a.css"))); |
| EXPECT_EQ(0, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(1, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_rewrites_completed()); |
| |
| CallFetcherCallbacks(); |
| // Second time we get both rewritten right. |
| ValidateExpected( |
| "trimmable2_now", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("a.css")), |
| StrCat(CssLinkHref(Encode("", "tw", "0", "a.css", "css")), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css")))); |
| EXPECT_EQ(0, metadata_cache_info().num_disabled_rewrites()); |
| // It's not deterministic whether the 2nd rewrite will get handled as |
| // a hit or repeated rewrite of same content. |
| EXPECT_EQ(2, (metadata_cache_info().num_repeated_rewrites() + |
| metadata_cache_info().num_hits())); |
| EXPECT_EQ(0, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(2, metadata_cache_info().num_rewrites_completed()); |
| |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimRepeatedNonOptimizable) { |
| // Make sure two instances of the same link are handled properly -- |
| // when optimization fails. |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| ValidateNoChanges("notrimmable2", |
| StrCat(CssLinkHref("b.css"), CssLinkHref("b.css"))); |
| } |
| |
| TEST_F(RewriteContextTest, TrimRepeated404) { |
| // Make sure two instances of the same link are handled properly -- |
| // when fetch fails. |
| InitTrimFilters(kRewrittenResource); |
| SetFetchResponse404("404.css"); |
| ValidateNoChanges("repeat404", |
| StrCat(CssLinkHref("404.css"), CssLinkHref("404.css"))); |
| } |
| |
| TEST_F(RewriteContextTest, FetchNonOptimizable) { |
| options()->set_implicit_cache_ttl_ms(kOriginTtlMs + 100 * Timer::kSecondMs); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| // We use MD5 hasher instead of mock hasher so that the we get the actual hash |
| // of the content and not hash 0 always. |
| UseMd5Hasher(); |
| |
| // Fetching a resource that's not optimizable under the rewritten URL |
| // should still work in a single-input case. This is important to be more |
| // robust against JS URL manipulation. |
| GoogleString output; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "0", "b.css", "css"), |
| &output, &headers)); |
| EXPECT_EQ("b", output); |
| |
| // Since this resource URL has a zero hash in it, this turns out to be a hash |
| // mismatch. So, cache TTL should be short and the result should be marked |
| // private. |
| EXPECT_FALSE(headers.IsProxyCacheable()); |
| EXPECT_TRUE(headers.IsBrowserCacheable()); |
| EXPECT_EQ(kOriginTtlMs + 0, |
| headers.CacheExpirationTimeMs() - timer()->NowMs()); |
| |
| // After 100 seconds, we'll only have 200 seconds left in the cache. |
| headers.Clear(); |
| output.clear(); |
| AdvanceTimeMs(200 * Timer::kSecondMs); |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "0", "b.css", "css"), |
| &output, &headers)); |
| EXPECT_EQ("b", output); |
| EXPECT_FALSE(headers.IsProxyCacheable()); |
| EXPECT_TRUE(headers.IsBrowserCacheable()); |
| EXPECT_EQ(kOriginTtlMs - 200 * Timer::kSecondMs, |
| headers.CacheExpirationTimeMs() - timer()->NowMs()); |
| } |
| |
| TEST_F(RewriteContextTest, FetchNonOptimizableWithPublicCaching) { |
| options()->set_implicit_cache_ttl_ms(kOriginTtlMs + 100 * Timer::kSecondMs); |
| options()->set_publicly_cache_mismatched_hashes_experimental(true); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| // We use MD5 hasher instead of mock hasher so that the we get the actual hash |
| // of the content and not hash 0 always. |
| UseMd5Hasher(); |
| |
| // Fetching a resource that's not optimizable under the rewritten URL |
| // should still work in a single-input case. This is important to be more |
| // robust against JS URL manipulation. |
| GoogleString output; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "0", "b.css", "css"), |
| &output, &headers)); |
| EXPECT_EQ("b", output); |
| |
| // Since this resource URL has a zero hash in it, this turns out to be a hash |
| // mismatch. However, the result should be proxy-cacheable and match the |
| // origin TTL, because we have specified |
| // set_publicly_cache_mismatched_hashes_experimental(true). |
| EXPECT_TRUE(headers.IsProxyCacheable()); |
| EXPECT_EQ(kOriginTtlMs + 0, |
| headers.CacheExpirationTimeMs() - timer()->NowMs()); |
| |
| // After 200 seconds, we'll only have 200 seconds left in the cache. |
| headers.Clear(); |
| output.clear(); |
| AdvanceTimeMs(200 * Timer::kSecondMs); |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "0", "b.css", "css"), |
| &output, &headers)); |
| EXPECT_EQ("b", output); |
| EXPECT_TRUE(headers.IsProxyCacheable()); |
| // We really want this to be (kOriginTtlMs + 0), not to have the TTL decay |
| // with elapased time. |
| EXPECT_EQ(kOriginTtlMs - 200 * Timer::kSecondMs, |
| headers.CacheExpirationTimeMs() - timer()->NowMs()); |
| } |
| |
| TEST_F(RewriteContextTest, FetchNonOptimizableLowTtl) { |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| // We use MD5 hasher instead of mock hasher so that the we get the actual hash |
| // of the content and not hash 0 always. |
| UseMd5Hasher(); |
| |
| // Fetching a resource that's not optimizable under the rewritten URL |
| // should still work in a single-input case. This is important to be more |
| // robust against JS URL manipulation. |
| GoogleString output; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "0", "e.css", "css"), |
| &output, &headers)); |
| EXPECT_EQ("e", output); |
| |
| ConstStringStarVector values; |
| headers.Lookup(HttpAttributes::kCacheControl, &values); |
| ASSERT_EQ(2, values.size()); |
| EXPECT_STREQ("max-age=5", *values[0]); |
| EXPECT_STREQ("private", *values[1]); |
| // Miss for request URL in http cache, metadata, and input resource. Insert |
| // metadata, and input and output resource with correct hash in http cache. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // We do a second fetch to trigger the case where the output resource for the |
| // URL from meta-data cache is found in http cache and hence we do not have |
| // the original input at the time we try to fix headers. |
| ClearStats(); |
| GoogleString output2; |
| ResponseHeaders headers2; |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "0", "e.css", "css"), |
| &output2, &headers2)); |
| EXPECT_EQ("e", output2); |
| |
| ConstStringStarVector values2; |
| headers2.Lookup(HttpAttributes::kCacheControl, &values2); |
| ASSERT_EQ(2, values2.size()); |
| EXPECT_STREQ("max-age=5", *values2[0]); |
| EXPECT_STREQ("private", *values2[1]); |
| // Miss for request URL. Hit for metadata and output resource with correct |
| // hash. |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, FetchNoSource) { |
| InitTrimFilters(kRewrittenResource); |
| SetFetchFailOnUnexpected(false); |
| EXPECT_FALSE( |
| TryFetchResource(Encode(kTestDomain, "tw", "0", "b.css", "css"))); |
| } |
| |
| // In the above tests, our URL fetcher called its callback directly, allowing |
| // the Rewrite to occur while the RewriteDriver was still attached. In this |
| // run, we will delay the URL fetcher's callback so that the initial Rewrite |
| // will not take place until after the HTML has been flushed. |
| TEST_F(RewriteContextTest, TrimDelayed) { |
| SetupWaitFetcher(); |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| ValidateNoChanges("trimmable", CssLinkHref("a.css")); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // Now we'll let the fetcher call its callbacks -- we'll see the |
| // cache-inserts now, and the next rewrite will succeed. |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // 2 because it's kOnTheFlyResource |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // The second cache time we request this URL, we should find no additional |
| // cache inserts or fetches. The rewrite should complete using a single |
| // cache hit for the metadata. No cache misses will occur. |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| } |
| |
| TEST_F(RewriteContextTest, TrimFetchOnTheFly) { |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| // The input URL is not in cache, but the fetch should work. |
| GoogleString content; |
| EXPECT_TRUE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a.css", "css", &content)); |
| EXPECT_EQ("a", content); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); // output + metadata. |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // input + metadata. |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| content.clear(); |
| |
| // Now fetch it again. This time the input URL is cached. |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "a.css", "css", |
| &content)); |
| EXPECT_EQ("a", content); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimFetchRewritten) { |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| // The input URL is not in cache, but the fetch should work. |
| GoogleString content; |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "a.css", "css", |
| &content)); |
| EXPECT_EQ("a", content); |
| EXPECT_EQ( |
| 0, server_context()->rewrite_stats()->cached_resource_fetches()->Get()); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| // TODO(jmarantz): have the lock-code return whether it had to wait to |
| // get the lock or was able to acquire it immediately to avoid the |
| // second cache lookup. |
| EXPECT_EQ(3, lru_cache()->num_misses()); // output, metadata, input |
| EXPECT_EQ(3, lru_cache()->num_inserts()); // output resource, input, metadata |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| content.clear(); |
| |
| // Now fetch it again: the output URL is cached. |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "a.css", "css", &content, |
| &headers)); |
| EXPECT_EQ("a", content); |
| EXPECT_EQ( |
| 1, server_context()->rewrite_stats()->cached_resource_fetches()->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| // Make sure headers are nice and long. |
| EXPECT_EQ(Timer::kYearMs, headers.cache_ttl_ms()); |
| EXPECT_TRUE(headers.IsProxyCacheable()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimFetchSeedsCache) { |
| // Make sure that rewriting on resource request also caches it for |
| // future use for HTML. |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| // The input URL is not in cache, but the fetch should work. |
| GoogleString content; |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "a.css", "css", |
| &content)); |
| EXPECT_EQ("a", content); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); // output, metadata, input |
| EXPECT_EQ(3, lru_cache()->num_inserts()); // output resource, input, metadata |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| ClearStats(); |
| |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // Just metadata |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); // cached. |
| } |
| |
| TEST_F(RewriteContextTest, TrimFetchRewriteFailureSeedsCache) { |
| // Make sure that rewriting on resource request also caches it for |
| // future use for HTML, in the case where the rewrite fails. |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| // The input URL is not in cache, but the fetch should work. |
| GoogleString content; |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "b.css", "css", |
| &content)); |
| EXPECT_EQ("b", content); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); // output, metadata, input |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // input, metadata |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| ClearStats(); |
| |
| ValidateNoChanges("nontrimmable", CssLinkHref("b.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // Just metadata |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); // cached. |
| } |
| |
| TEST_F(RewriteContextTest, TrimFetch404SeedsCache) { |
| // Check that we cache a 404, and cache it for a reasonable amount of time. |
| InitTrimFilters(kRewrittenResource); |
| SetFetchResponse404("404.css"); |
| |
| GoogleString content; |
| EXPECT_FALSE(FetchResource( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "404.css", "css", |
| &content)); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Should cache immediately... |
| ValidateNoChanges("404", CssLinkHref("404.css")); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // ... but not for too long. |
| AdvanceTimeMs(Timer::kDayMs); |
| ValidateNoChanges("404", CssLinkHref("404.css")); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Verifies that rewriters can replace resource URLs without kicking off any |
| // fetching or caching. |
| TEST_F(RewriteContextTest, ClobberResourceUrlSync) { |
| InitTrimFiltersSync(kOnTheFlyResource); |
| InitResources(); |
| GoogleString input_html(CssLinkHref("a_private.css")); |
| // TODO(sligocki): Why is this an absolute URL? |
| GoogleString output_html(CssLinkHref( |
| Encode(kTestDomain, "ts", "0", "a_private.css", "css"))); |
| ValidateExpected("trimmable", input_html, output_html); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| } |
| |
| // Verifies that when an HTML document references an uncacheable resource, that |
| // reference does not get modified. |
| TEST_F(RewriteContextTest, DoNotModifyReferencesToUncacheableResources) { |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| GoogleString input_html(CssLinkHref("a_private.css")); |
| |
| ValidateExpected("trimmable_but_private", input_html, input_html); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); // partition, resource |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // partition, not-cacheable memo |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); // the resource |
| ClearStats(); |
| |
| ValidateExpected("trimmable_but_private", input_html, input_html); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // not-cacheable memo |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| ValidateExpected("trimmable_but_private", input_html, input_html); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // not-cacheable memo |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Verifies that when an HTML document references an uncacheable resource, that |
| // reference does get modified if cache ttl overriding is enabled. |
| TEST_F(RewriteContextTest, CacheTtlOverridingForPrivateResources) { |
| FetcherUpdateDateHeaders(); |
| int64 ttl_ms = 600 * 1000; |
| // Start with overriding caching for a wildcard pattern that does not match |
| // the css url. |
| options()->AddOverrideCacheTtl("*b_private*"); |
| options()->set_override_caching_ttl_ms(ttl_ms); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| GoogleString input_html(CssLinkHref("a_private.css")); |
| ValidateNoChanges("trimmable_not_overridden", input_html); |
| ClearStats(); |
| |
| // Now override caching for a pattern that matches the css url. |
| options()->ClearSignatureForTesting(); |
| options()->AddOverrideCacheTtl("*a_private*"); |
| server_context()->ComputeSignature(options()); |
| |
| GoogleString output_html(CssLinkHref( |
| Encode("", "tw", "0", "a_private.css", "css"))); |
| |
| // The private resource gets rewritten. |
| ValidateExpected("trimmable_but_private", input_html, output_html); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| AdvanceTimeMs(5 * 1000); |
| |
| // Advance the timer by 5 seconds. Gets rewritten again with no extra fetches. |
| ValidateExpected("trimmable_but_private", input_html, output_html); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| AdvanceTimeMs((ttl_ms * 4) / 5); |
| // Advance past the freshening threshold. The resource gets freshened and we |
| // update the metadata cache. |
| ValidateExpected("trimmable_but_private", input_html, output_html); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // Send another request after freshening. Succeeds without any extra fetches. |
| ValidateExpected("trimmable_but_private", input_html, output_html); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| AdvanceTimeMs(2 * ttl_ms); |
| // Advance past expiry. We fetch the resource and update the HTTPCache and |
| // metadata cache. |
| ValidateExpected("trimmable_but_private", input_html, output_html); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| SetupWaitFetcher(); |
| AdvanceTimeMs(2 * ttl_ms); |
| // Advance past expiry. We fetch the resource and update the HTTPCache and |
| // metadata cache. |
| ValidateExpected("trimmable_but_private", input_html, input_html); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Make sure that cache-control: no-transform is honored. |
| TEST_F(RewriteContextTest, HonorNoTransform) { |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| GoogleString content; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a_no_transform.css", "css", &content, &headers)); |
| EXPECT_EQ(" a ", content); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| // Lookup the output resource, input resource, and metadata. |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // meta data & original |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| ClearStats(); |
| EXPECT_TRUE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a_no_transform.css", "css", &content, &headers)); |
| EXPECT_EQ(" a ", content); |
| EXPECT_EQ(2, lru_cache()->num_hits()); // meta data & original |
| EXPECT_EQ(1, lru_cache()->num_misses()); // output resource |
| EXPECT_EQ(0, lru_cache()->num_inserts()); // name mapping & original |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| // Now with the option set to false, no-transform shall NOT be honored and |
| // resource is rewritten. |
| ClearStats(); |
| options()->ClearSignatureForTesting(); |
| options()->set_disable_rewrite_on_no_transform(false); |
| options()->ComputeSignature(); |
| EXPECT_TRUE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a_no_transform.css", "css", &content, &headers)); |
| EXPECT_EQ("a", content); |
| // TODO(mpalem): Verify the following comments are accurate. |
| EXPECT_EQ(1, lru_cache()->num_hits()); // original |
| // output resource and metadata |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // metadata & output resource |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Verifies that we can rewrite uncacheable resources without caching them. |
| TEST_F(RewriteContextTest, FetchUncacheableWithRewritesInLineOfServing) { |
| InitTrimFiltersSync(kOnTheFlyResource); |
| InitResources(); |
| |
| GoogleString content; |
| |
| // The first time we serve the resource, we insert a memo that it is |
| // uncacheable, and a name mapping. |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, TrimWhitespaceSyncFilter::kFilterId, |
| "a_private.css", |
| "css", |
| &content)); |
| EXPECT_EQ("a", content); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // name mapping & uncacheable memo |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Each subsequent time we serve the resource, we should experience a cache |
| // hit for the notation that the resource is uncacheable, and then we should |
| // perform an origin fetch anyway. |
| for (int i = 0; i < 3; ++i) { |
| ClearStats(); |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_private.css", |
| "css", |
| &content)); |
| EXPECT_EQ("a", content); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Now, we change the resource. |
| ResponseHeaders private_css_header; |
| private_css_header.set_major_version(1); |
| private_css_header.set_minor_version(1); |
| private_css_header.SetStatusAndReason(HttpStatus::kOK); |
| private_css_header.SetDateAndCaching(http_cache()->timer()->NowMs(), |
| kOriginTtlMs, |
| ", private"); |
| private_css_header.ComputeCaching(); |
| |
| SetFetchResponse("http://test.com/a_private.css", |
| private_css_header, |
| " b "); |
| |
| // We should continue to experience cache hits, and continue to fetch from |
| // the origin. |
| for (int i = 0; i < 3; ++i) { |
| ClearStats(); |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_private.css", |
| "css", |
| &content)); |
| EXPECT_EQ("b", content); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| ClearStats(); |
| |
| // After advancing the time, we should see new cache inserts. Note that we |
| // also get a cache hit because the out-of-date entries are still there. |
| AdvanceTimeMs(Timer::kMinuteMs * 50); |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_private.css", |
| "css", |
| &content)); |
| EXPECT_EQ("b", content); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Verifies that we preserve cache-control when rewriting a no-cache resource. |
| TEST_F(RewriteContextTest, PreserveNoCacheWithRewrites) { |
| InitTrimFiltersSync(kOnTheFlyResource); |
| InitResources(); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| // Even on sequential requests, the resource does not become cache-extended. |
| for (int i = 0; i < 4; ++i) { |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_no_cache.css", |
| "css", |
| &content, |
| &headers)); |
| EXPECT_EQ("a", content); |
| ConstStringStarVector values; |
| headers.Lookup(HttpAttributes::kCacheControl, &values); |
| ASSERT_EQ(2, values.size()); |
| EXPECT_STREQ("max-age=0", *values[0]); |
| EXPECT_STREQ("no-cache", *values[1]); |
| } |
| } |
| |
| TEST_F(RewriteContextTest, PreserveNoCacheWithFailedRewrites) { |
| // Make sure propagation of non-cacheability works in case when |
| // rewrite failed. (This relies on cache extender explicitly |
| // rejecting to rewrite non-cacheable things). |
| options()->EnableFilter(RewriteOptions::kExtendCacheCss); |
| rewrite_driver()->AddFilters(); |
| |
| InitResources(); |
| |
| // Even on sequential requests, the resource does not become cache-extended. |
| for (int i = 0; i < 4; ++i) { |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| "ce", |
| "a_no_cache.css", |
| "css", |
| &content, |
| &headers)); |
| EXPECT_EQ(" a ", content); |
| ConstStringStarVector values; |
| headers.Lookup(HttpAttributes::kCacheControl, &values); |
| ASSERT_EQ(2, values.size()); |
| EXPECT_STREQ("max-age=0", *values[0]); |
| EXPECT_STREQ("no-cache", *values[1]); |
| } |
| } |
| |
| TEST_F(RewriteContextTest, TestRewritesOnEmptyPublicResources) { |
| options()->EnableFilter(RewriteOptions::kExtendCacheCss); |
| rewrite_driver()->AddFilters(); |
| EnableDebug(); |
| |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = ""; |
| |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| for (int i = 0; i < 2; i++) { |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, "ce", "test.css", "css", &content, &headers)); |
| EXPECT_EQ("", content); |
| EXPECT_STREQ("max-age=31536000", |
| headers.Lookup1(HttpAttributes::kCacheControl)); |
| } |
| } |
| |
| TEST_F(RewriteContextTest, TestRewritesOnEmptyPrivateResources) { |
| options()->EnableFilter(RewriteOptions::kExtendCacheCss); |
| rewrite_driver()->AddFilters(); |
| EnableDebug(); |
| |
| const char kPath[] = "test.css"; |
| ResponseHeaders no_store_css_header; |
| int64 now_ms = timer()->NowMs(); |
| no_store_css_header.set_major_version(1); |
| no_store_css_header.set_minor_version(1); |
| no_store_css_header.SetStatusAndReason(HttpStatus::kOK); |
| no_store_css_header.SetDateAndCaching(now_ms, 0, ",no-store"); |
| no_store_css_header.Add(HttpAttributes::kContentType, "text/css"); |
| no_store_css_header.ComputeCaching(); |
| |
| SetFetchResponse(AbsolutifyUrl(kPath), no_store_css_header, ""); |
| |
| for (int i = 0; i < 2; i++) { |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, "ce", "test.css", "css", &content, &headers)); |
| EXPECT_EQ("", content); |
| ConstStringStarVector values; |
| headers.Lookup(HttpAttributes::kCacheControl, &values); |
| ASSERT_EQ(3, values.size()); |
| EXPECT_STREQ("max-age=0", *values[0]); |
| EXPECT_STREQ("no-cache", *values[1]); |
| EXPECT_STREQ("no-store", *values[2]); |
| } |
| } |
| |
| TEST_F(RewriteContextTest, EmptyInputResources) { |
| options()->EnableFilter(RewriteOptions::kRewriteCss); |
| rewrite_driver()->AddFilters(); |
| EnableDebug(); |
| |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = ""; |
| |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| for (int i = 0; i < 2; i++) { |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, "cf", "test.css", "css", &content, &headers)); |
| // Loads empty result. |
| EXPECT_EQ("", content); |
| |
| // But only with private 5min cache lifetime to avoid spreading unexpected |
| // empty resource. |
| ConstStringStarVector values; |
| EXPECT_TRUE(headers.Lookup(HttpAttributes::kCacheControl, &values)); |
| ASSERT_EQ(2, values.size()); |
| EXPECT_STREQ("max-age=300", *values[0]); |
| EXPECT_STREQ("private", *values[1]); |
| } |
| } |
| |
| TEST_F(RewriteContextTest, EmptyOutputResources) { |
| options()->EnableFilter(RewriteOptions::kRewriteCss); |
| rewrite_driver()->AddFilters(); |
| EnableDebug(); |
| |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " "; |
| |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| for (int i = 0; i < 2; i++) { |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, "cf", "test.css", "css", &content, &headers)); |
| EXPECT_EQ("", content); // Result is empty. That's fine. |
| // And serves with full public 1year cache lifetime. |
| EXPECT_STREQ("max-age=31536000", |
| headers.Lookup1(HttpAttributes::kCacheControl)); |
| } |
| } |
| |
| // Verifies that we preserve cache-control when rewriting a no-cache resource |
| // with a non-on-the-fly filter |
| TEST_F(RewriteContextTest, PrivateNotCached) { |
| InitTrimFiltersSync(kRewrittenResource); |
| InitResources(); |
| |
| // Even on sequential requests, the resource does not become cache-extended. |
| for (int i = 0; i < 4; ++i) { |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| // There are two possible secure outcomes here: either the fetch fails |
| // entirely here, or we serve it as cache-control: private. |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_private.css", |
| "css", |
| &content, |
| &headers)); |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "private")); |
| } |
| |
| // Now make sure that fetching with an invalid hash doesn't work when |
| // the original is not available. This is significant since if it this fails |
| // an attacker may get access to resources without access to an actual hash. |
| GoogleString output; |
| mock_url_fetcher()->Disable(); |
| EXPECT_FALSE(FetchResourceUrl( |
| Encode(kTestDomain, TrimWhitespaceSyncFilter::kFilterId, "1", |
| "a_private.css", "css"), |
| &output)); |
| } |
| |
| TEST_F(RewriteContextTest, PrivateNotCachedOnTheFly) { |
| // Variant of the above for on-the-fly, as that relies on completely |
| // different code paths to be safe. (It is also covered by earlier tests, |
| // but this is included here to be thorough). |
| InitTrimFiltersSync(kOnTheFlyResource); |
| InitResources(); |
| |
| // Even on sequential requests, the resource does not become cache-extended. |
| for (int i = 0; i < 4; ++i) { |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_private.css", |
| "css", |
| &content, |
| &headers)); |
| EXPECT_EQ("a", content); |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "private")) |
| << " Not private on fetch #" << i << " " << headers.ToString(); |
| } |
| |
| // Now make sure that fetching with an invalid hash doesn't work when |
| // the original is not available. This is significant since if it this fails |
| // an attacker may get access to resources without access to an actual hash. |
| GoogleString output; |
| mock_url_fetcher()->Disable(); |
| EXPECT_FALSE(FetchResourceUrl( |
| Encode(kTestDomain, TrimWhitespaceSyncFilter::kFilterId, "1", |
| "a_private.css", "css"), |
| &output)); |
| } |
| |
| // Verifies that we preserve cache-control when rewriting a no-store resource. |
| TEST_F(RewriteContextTest, PreserveNoStoreWithRewrites) { |
| InitTrimFiltersSync(kOnTheFlyResource); |
| InitResources(); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| // Even on sequential requests, the resource does not become cache-extended. |
| for (int i = 0; i < 4; ++i) { |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_no_store.css", |
| "css", |
| &content, |
| &headers)); |
| EXPECT_EQ("a", content); |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "max-age=0")); |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "no-cache")); |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "no-store")); |
| } |
| } |
| |
| // Verifies that we preserve cache-control when rewriting a private resource. |
| TEST_F(RewriteContextTest, PreservePrivateWithRewrites) { |
| InitTrimFiltersSync(kOnTheFlyResource); |
| InitResources(); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| // Even on sequential requests, the resource does not become cache-extended. |
| for (int i = 0; i < 4; ++i) { |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_private.css", |
| "css", |
| &content, |
| &headers)); |
| EXPECT_EQ("a", content); |
| ConstStringStarVector values; |
| headers.Lookup(HttpAttributes::kCacheControl, &values); |
| ASSERT_EQ(2, values.size()); |
| EXPECT_STREQ(OriginTtlMaxAge(), *values[0]); |
| EXPECT_STREQ("private", *values[1]); |
| } |
| } |
| |
| // Verifies that we intersect cache-control when there are multiple input |
| // resources. |
| TEST_F(RewriteContextTest, CacheControlWithMultipleInputResources) { |
| InitCombiningFilter(0); |
| EnableDebug(); |
| combining_filter_->set_on_the_fly(true); |
| InitResources(); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| GoogleString combined_url = |
| Encode(kTestDomain, CombiningFilter::kFilterId, "0", |
| MultiUrl("a.css", |
| "b.css", |
| "a_private.css"), "css"); |
| |
| FetchResourceUrl(combined_url, &content, &headers); |
| EXPECT_EQ(" a b a ", content); |
| |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(4, lru_cache()->num_misses()); // 3 inputs. |
| EXPECT_EQ(4, lru_cache()->num_inserts()) << |
| "partition, 2 inputs, 1 non-cacheability note"; |
| EXPECT_EQ(3, counting_url_async_fetcher()->fetch_count()); |
| |
| ConstStringStarVector values; |
| headers.Lookup(HttpAttributes::kCacheControl, &values); |
| ASSERT_EQ(2, values.size()); |
| EXPECT_STREQ(OriginTtlMaxAge(), *values[0]); |
| EXPECT_STREQ("private", *values[1]); |
| } |
| |
| // Fetching & reconstructing a combined resource with a healthy cache. |
| TEST_F(RewriteContextTest, CombineFetchHealthyCache) { |
| InitCombiningFilter(0); |
| EnableDebug(); |
| InitResources(); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| GoogleString combined_url = |
| Encode(kTestDomain, CombiningFilter::kFilterId, "0", |
| MultiUrl("a.css", "b.css"), "css"); |
| FetchResourceUrl(combined_url, &content, &headers); |
| EXPECT_EQ(" a b", content); |
| |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(4, lru_cache()->num_misses()) |
| << "output, metadata, 2 inputs"; |
| EXPECT_EQ(4, lru_cache()->num_inserts()) << "ouptput, metadata, 2 inputs"; |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| |
| // Now do the fetch again and we will get everything we need in one |
| // cache lookup. |
| ClearStats(); |
| content.clear(); |
| FetchResourceUrl(combined_url, &content, &headers); |
| EXPECT_EQ(" a b", content); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Fetching & reconstructing a combined resource with an unhealthy cache. |
| TEST_F(RewriteContextTest, CombineFetchUnhealthyCache) { |
| lru_cache()->set_is_healthy(false); |
| InitCombiningFilter(0); |
| EnableDebug(); |
| InitResources(); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| GoogleString combined_url = |
| Encode(kTestDomain, CombiningFilter::kFilterId, "0", |
| MultiUrl("a.css", |
| "b.css"), "css"); |
| FetchResourceUrl(combined_url, &content, &headers); |
| EXPECT_EQ(" a b", content); |
| |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| |
| // Now do the fetch again. Because we have no cache, we must fetch |
| // the inputs & recombine them, so the stats are exactly the same. |
| ClearStats(); |
| content.clear(); |
| FetchResourceUrl(combined_url, &content, &headers); |
| EXPECT_EQ(" a b", content); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| |
| // Verifies that we intersect cache-control when there are multiple input |
| // resources. |
| TEST_F(RewriteContextTest, CacheControlWithMultipleInputResourcesAndNoStore) { |
| InitCombiningFilter(0); |
| EnableDebug(); |
| combining_filter_->set_on_the_fly(true); |
| InitResources(); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| GoogleString combined_url = |
| Encode(kTestDomain, CombiningFilter::kFilterId, "0", |
| MultiUrl("a.css", |
| "b.css", |
| "a_private.css", |
| "a_no_store.css"), "css"); |
| |
| FetchResourceUrl(combined_url, &content, &headers); |
| EXPECT_EQ(" a b a a ", content); |
| |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(5, lru_cache()->num_misses()); // 4 inputs + metadata. |
| EXPECT_EQ(5, lru_cache()->num_inserts()) |
| << "partition, 2 inputs, 2 non-cacheability notes"; |
| EXPECT_EQ(4, counting_url_async_fetcher()->fetch_count()); |
| |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "max-age=0")); |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "no-cache")); |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "no-store")); |
| } |
| |
| // Verifies that we cache-extend when rewriting a cacheable resource. |
| TEST_F(RewriteContextTest, CacheExtendCacheableResource) { |
| InitTrimFiltersSync(kOnTheFlyResource); |
| InitResources(); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| for (int i = 0; i < 4; ++i) { |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a.css", |
| "css", |
| &content, |
| &headers)); |
| EXPECT_EQ("a", content); |
| EXPECT_STREQ(StringPrintf("max-age=%lld", |
| static_cast<long long int>( |
| ServerContext::kGeneratedMaxAgeMs / 1000)), |
| headers.Lookup1(HttpAttributes::kCacheControl)); |
| } |
| } |
| |
| // Make sure we preserve the charset properly. |
| TEST_F(RewriteContextTest, PreserveCharsetRewritten) { |
| InitResources(); |
| InitTrimFiltersSync(kRewrittenResource); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_ru.css", |
| "css", |
| &content, |
| &headers)); |
| EXPECT_STREQ("text/css; charset=koi8-r", |
| headers.Lookup1(HttpAttributes::kContentType)); |
| } |
| |
| TEST_F(RewriteContextTest, PreserveCharsetOnTheFly) { |
| InitResources(); |
| InitTrimFiltersSync(kOnTheFlyResource); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResource(kTestDomain, |
| TrimWhitespaceSyncFilter::kFilterId, |
| "a_ru.css", |
| "css", |
| &content, |
| &headers)); |
| EXPECT_STREQ("text/css; charset=koi8-r", |
| headers.Lookup1(HttpAttributes::kContentType)); |
| } |
| |
| TEST_F(RewriteContextTest, PreserveCharsetNone) { |
| // Null test -- make sure we don't invent a charset when there is none. |
| InitResources(); |
| InitTrimFiltersSync(kRewrittenResource); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResource( |
| kTestDomain, TrimWhitespaceSyncFilter::kFilterId, "a.css", "css", |
| &content, &headers)); |
| EXPECT_STREQ("text/css", headers.Lookup1(HttpAttributes::kContentType)); |
| } |
| |
| // Make sure we preserve charset across 2 filters. |
| TEST_F(RewriteContextTest, CharsetTwoFilters) { |
| InitTwoFilters(kRewrittenResource); |
| InitResources(); |
| |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| GoogleString url = Encode("", "tw", "0", |
| Encode("", "uc", "0", "a_ru.css", "css"), "css"); |
| // Need to rewrite HTML first as our test filters aren't registered and hence |
| // can't reconstruct. |
| ValidateExpected("two_filters", |
| CssLinkHref("a_ru.css"), |
| CssLinkHref(url)); |
| |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, url), &content, &headers)); |
| EXPECT_STREQ("text/css; charset=koi8-r", |
| headers.Lookup1(HttpAttributes::kContentType)); |
| } |
| |
| TEST_F(RewriteContextTest, FetchColdCacheOnTheFly) { |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| ClearStats(); |
| TestServeFiles(&kContentTypeCss, TrimWhitespaceRewriter::kFilterId, "css", |
| "a.css", " a ", |
| "a.css", "a"); |
| } |
| |
| TEST_F(RewriteContextTest, TrimFetchWrongHash) { |
| // Test to see that fetches from wrong hash can fallback to the |
| // correct one mentioned in metadata correctly. |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| // First rewrite a page to get the right hash remembered |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| ClearStats(); |
| |
| // Now try fetching it with the wrong hash) |
| GoogleString contents; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "1", "a.css", "css"), |
| &contents, &headers)); |
| EXPECT_STREQ("a", contents); |
| // Should not need any rewrites or fetches. |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| // Should have 2 hits: metadata and .0., and 2 misses on wrong-hash version |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, lru_cache()->num_identical_reinserts()); |
| |
| // Make sure the TTL is correct, and the result is private. |
| EXPECT_EQ(RewriteOptions::kDefaultImplicitCacheTtlMs, |
| headers.cache_ttl_ms()); |
| EXPECT_FALSE(headers.IsProxyCacheable()); |
| EXPECT_TRUE(headers.IsBrowserCacheable()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimFetchWrongHashColdCache) { |
| // Tests fetch with wrong hash when we did not originally create |
| // the version with the right hash. |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| GoogleString contents; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "1", "a.css", "css"), |
| &contents, &headers)); |
| EXPECT_STREQ("a", contents); |
| |
| // Make sure the TTL is correct (short), and the result is private. |
| EXPECT_EQ(RewriteOptions::kDefaultImplicitCacheTtlMs, |
| headers.cache_ttl_ms()); |
| EXPECT_FALSE(headers.IsProxyCacheable()); |
| EXPECT_TRUE(headers.IsBrowserCacheable()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimFetchHashFailed) { |
| // Test to see that if we try to fetch a rewritten version (with a pagespeed |
| // resource URL) when metadata cache indicates rewrite of original failed |
| // that we will quickly fallback to original without attempting rewrite. |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| ClearStats(); |
| |
| GoogleString contents; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "1", "b.css", "css"), |
| &contents, &headers)); |
| EXPECT_STREQ("b", contents); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| // Should have 2 hits: metadata and .0., and 2 misses on wrong-hash version |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, lru_cache()->num_identical_reinserts()); |
| |
| // Make sure the TTL is correct, and the result is private. |
| EXPECT_EQ(RewriteOptions::kDefaultImplicitCacheTtlMs, |
| headers.cache_ttl_ms()); |
| EXPECT_FALSE(headers.IsProxyCacheable()); |
| EXPECT_TRUE(headers.IsBrowserCacheable()); |
| } |
| |
| TEST_F(RewriteContextTest, TrimFetchHashFailedShortTtl) { |
| // Variation of TrimFetchHashFailed, where the input's TTL is very short. |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| ValidateNoChanges("no_trimmable", CssLinkHref("d.css")); |
| ClearStats(); |
| |
| GoogleString contents; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(Encode(kTestDomain, "tw", "1", "d.css", "css"), |
| &contents, &headers)); |
| EXPECT_STREQ("d", contents); |
| EXPECT_EQ(kLowOriginTtlMs, headers.cache_ttl_ms()); |
| EXPECT_FALSE(headers.IsProxyCacheable()); |
| EXPECT_TRUE(headers.IsBrowserCacheable()); |
| } |
| |
| TEST_F(RewriteContextTest, FetchColdCacheRewritten) { |
| InitTrimFilters(kOnTheFlyResource); |
| InitResources(); |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(1, fetch_successes_->Get()); |
| EXPECT_EQ(0, fetch_failures_->Get()); |
| ClearStats(); |
| TestServeFiles(&kContentTypeCss, TrimWhitespaceRewriter::kFilterId, "css", |
| "a.css", " a ", |
| "a.css", "a"); |
| // TestServeFiles clears cache so we need to re-fetch. |
| EXPECT_EQ(1, fetch_successes_->Get()); |
| EXPECT_EQ(0, fetch_failures_->Get()); |
| } |
| |
| TEST_F(RewriteContextTest, OnTheFlyNotFound) { |
| InitTrimFilters(kOnTheFlyResource); |
| |
| // note: no InitResources so we'll get a file-not-found. |
| SetFetchFailOnUnexpected(false); |
| |
| // In this case, the resource is optimizable but we'll fail to fetch it. |
| ValidateNoChanges("no_trimmable", CssLinkHref("a.css")); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, fetch_failures_->Get()); |
| EXPECT_EQ(0, fetch_successes_->Get()); |
| ClearStats(); |
| |
| // We should have cached the failed rewrite, no misses, fetches, or inserts. |
| ValidateNoChanges("no_trimmable", CssLinkHref("a.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, fetch_failures_->Get()); |
| EXPECT_EQ(0, fetch_successes_->Get()); |
| } |
| |
| TEST_F(RewriteContextTest, RewrittenNotFound) { |
| InitTrimFilters(kRewrittenResource); |
| |
| // note: no InitResources so we'll get a file-not found. |
| SetFetchFailOnUnexpected(false); |
| |
| // In this case, the resource is optimizable but we'll fail to fetch it. |
| ValidateNoChanges("no_trimmable", CssLinkHref("a.css")); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // We should have cached the failed rewrite, no misses, fetches, or inserts. |
| ValidateNoChanges("no_trimmable", CssLinkHref("a.css")); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // In this testcase we'll attempt to serve a rewritten resource, but having |
| // failed to call InitResources we will not be able to do the on-the-fly |
| // rewrite. |
| TEST_F(RewriteContextTest, FetchColdCacheOnTheFlyNotFound) { |
| InitTrimFilters(kOnTheFlyResource); |
| |
| // note: no InitResources so we'll get a file-not found. |
| SetFetchFailOnUnexpected(false); |
| |
| GoogleString content; |
| EXPECT_FALSE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a.css", "css", &content)); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // fetch failure, metadata. |
| EXPECT_EQ(0, lru_cache()->num_identical_reinserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // Try it again with a warm cache. We'll get a 'hit' which will inform us |
| // that this resource is not fetchable. |
| EXPECT_FALSE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a.css", "css", &content)); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); // We "remember" the fetch failure |
| EXPECT_EQ(0, lru_cache()->num_identical_reinserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Same testcase, but with a non-on-the-fly resource. |
| TEST_F(RewriteContextTest, FetchColdCacheRewrittenNotFound) { |
| InitTrimFilters(kRewrittenResource); |
| |
| // note: no InitResources so we'll get a file-not found. |
| SetFetchFailOnUnexpected(false); |
| |
| GoogleString content; |
| EXPECT_FALSE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a.css", "css", &content)); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| |
| // We lookup the output resource plus the inputs and metadata. |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| |
| // We remember the fetch failure, and the failed rewrite. |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| // Try it again with a warm cache. We'll get a number of hits which will |
| // inform us that this resource is not fetchable: |
| // - a metadata entry stating there is no successful rewrite. |
| // - HTTP cache entry for resource fetch of original failing |
| // - 2nd access of it when we give up on fast path. |
| // TODO(morlovich): Should we propagate the 404 directly? |
| EXPECT_FALSE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a.css", "css", &content)); |
| EXPECT_EQ(3, lru_cache()->num_hits()); |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TwoFilters) { |
| InitTwoFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| ValidateExpected("two_filters", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", |
| Encode("", "uc", "0", "a.css", "css"), |
| "css"))); |
| } |
| |
| TEST_F(RewriteContextTest, TwoFiltersDelayedFetches) { |
| SetupWaitFetcher(); |
| InitTwoFilters(kOnTheFlyResource); |
| InitResources(); |
| |
| ValidateNoChanges("trimmable1", CssLinkHref("a.css")); |
| CallFetcherCallbacks(); |
| ValidateExpected("delayed_fetches", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", |
| Encode("", "uc", "0", "a.css", "css"), |
| "css"))); |
| } |
| |
| TEST_F(RewriteContextTest, RepeatedTwoFilters) { |
| // Make sure if we have repeated URLs and chaining, it still works right. |
| InitTwoFilters(kRewrittenResource); |
| InitResources(); |
| |
| ValidateExpected( |
| "two_filters2", StrCat(CssLinkHref("a.css"), CssLinkHref("a.css")), |
| StrCat(CssLinkHref(Encode("", "tw", "0", |
| Encode("", "uc", "0", "a.css", "css"), "css")), |
| CssLinkHref(Encode("", "tw", "0", |
| Encode("", "uc", "0", "a.css", "css"), "css")))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| } |
| |
| TEST_F(RewriteContextTest, ReconstructChainedWrongHash) { |
| // Make sure that we don't have problems with repeated reconstruction |
| // of chained rewrites where the hash is incorrect. (We used to screw |
| // up the response code if two different wrong inner hashes were used, |
| // leading to failure at outer level). Also make sure we always propagate |
| // short TTL as well, since that could also be screwed up. |
| |
| // Need normal filters since cloned RewriteDriver instances wouldn't |
| // know about test-only stuff. |
| options()->EnableFilter(RewriteOptions::kCombineCss); |
| options()->EnableFilter(RewriteOptions::kRewriteCss); |
| rewrite_driver()->AddFilters(); |
| |
| SetResponseWithDefaultHeaders( |
| "a.css", kContentTypeCss, " div { display: block; }", 100); |
| |
| GoogleString url = Encode(kTestDomain, "cc", "0", |
| Encode("", "cf", "1", "a.css", "css"), "css"); |
| GoogleString url2 = Encode(kTestDomain, "cc", "0", |
| Encode("", "cf", "2", "a.css", "css"), "css"); |
| |
| for (int run = 0; run < 3; ++run) { |
| GoogleString content; |
| ResponseHeaders headers; |
| |
| FetchResourceUrl(url, &content, &headers); |
| // Note that this works only because the combiner fails and passes |
| // through its input, which is the private cache-controlled output |
| // of rewrite_css |
| EXPECT_EQ(HttpStatus::kOK, headers.status_code()); |
| EXPECT_STREQ("div{display:block}", content); |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "private")) |
| << headers.ToString(); |
| } |
| |
| // Now also try the second version. |
| GoogleString content; |
| ResponseHeaders headers; |
| FetchResourceUrl(url2, &content, &headers); |
| EXPECT_EQ(HttpStatus::kOK, headers.status_code()); |
| EXPECT_STREQ("div{display:block}", content); |
| EXPECT_TRUE(headers.HasValue(HttpAttributes::kCacheControl, "private")) |
| << headers.ToString(); |
| } |
| |
| TEST_F(RewriteContextTest, NestedRewriteWith404) { |
| ResponseHeaders default_css_header; |
| SetDefaultLongCacheHeaders(&kContentTypeCss, &default_css_header); |
| int64 now_ms = http_cache()->timer()->NowMs(); |
| default_css_header.SetDateAndCaching(now_ms, 3 * kOriginTtlMs); |
| default_css_header.ComputeCaching(); |
| SetFetchResponse(StrCat(kTestDomain, "x.css"), default_css_header, |
| "a.css\n404.css\n"); |
| SetFetchResponse404("404.css"); |
| options()->set_implicit_cache_ttl_ms(kOriginTtlMs / 4); |
| |
| const GoogleString kRewrittenUrl = Encode( |
| "", NestedFilter::kFilterId, "0", "x.css", "css"); |
| InitNestedFilter(NestedFilter::kExpectNestedRewritesSucceed); |
| nested_filter_->set_check_nested_rewrite_result(false); |
| InitResources(); |
| ValidateExpected("async3", CssLinkHref("x.css"), CssLinkHref(kRewrittenUrl)); |
| |
| // Cache misses for the 3 resources, and 3 metadata lookups. We insert the 6 |
| // items above, and the rewritten resource into cache. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(6, lru_cache()->num_misses()); |
| EXPECT_EQ(7, lru_cache()->num_inserts()); |
| EXPECT_EQ(3, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| ClearStats(); |
| |
| AdvanceTimeMs(kOriginTtlMs / 4); |
| ValidateExpected("async3", CssLinkHref("x.css"), CssLinkHref(kRewrittenUrl)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| |
| ClearStats(); |
| AdvanceTimeMs(kOriginTtlMs / 4); |
| |
| SetupWaitFetcher(); |
| ValidateNoChanges("async3", CssLinkHref("x.css")); |
| CallFetcherCallbacks(); |
| // metadata (3). x.css and 404.css in the HTTP cache. |
| EXPECT_EQ(5, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| // Inserts in both metadata cache and HTTP cache for x.css and 404.css. |
| EXPECT_EQ(4, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, http_cache()->cache_expirations()->Get()); |
| ClearStats(); |
| } |
| |
| TEST_F(RewriteContextTest, NestedLogging) { |
| ResponseHeaders default_css_header; |
| SetDefaultLongCacheHeaders(&kContentTypeCss, &default_css_header); |
| int64 now_ms = http_cache()->timer()->NowMs(); |
| default_css_header.SetDateAndCaching(now_ms, 3 * kOriginTtlMs); |
| default_css_header.ComputeCaching(); |
| SetFetchResponse(StrCat(kTestDomain, "x.css"), default_css_header, |
| "a.css\nb.css\n"); |
| |
| const GoogleString kRewrittenUrl = Encode( |
| "", NestedFilter::kFilterId, "0", "x.css", "css"); |
| InitNestedFilter(NestedFilter::kExpectNestedRewritesSucceed); |
| InitResources(); |
| ValidateExpected("async3", CssLinkHref("x.css"), CssLinkHref(kRewrittenUrl)); |
| |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(6, lru_cache()->num_misses()); |
| EXPECT_EQ(7, lru_cache()->num_inserts()); |
| EXPECT_EQ(3, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| // The following would be 3 if we also logged for nested rewrites. |
| EXPECT_EQ(1, logging_info()->metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_hits()); |
| ClearStats(); |
| |
| GoogleString rewritten_contents; |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, kRewrittenUrl), |
| &rewritten_contents)); |
| // Note: These tests do not use HtmlResourceSlots and thus they do not |
| // preserve URL relativity. |
| EXPECT_EQ(StrCat(Encode(kTestDomain, "uc", "0", "a.css", "css"), "\n", |
| Encode(kTestDomain, "uc", "0", "b.css", "css"), "\n"), |
| rewritten_contents); |
| // HTTP cache hit for rewritten URL. |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_hits()); |
| ClearStats(); |
| |
| ValidateExpected("async3", CssLinkHref("x.css"), CssLinkHref(kRewrittenUrl)); |
| // Completes with a single hit for meta-data. |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_expirations()->Get()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(1, logging_info()->metadata_cache_info().num_hits()); |
| ClearStats(); |
| |
| AdvanceTimeMs(2 * kOriginTtlMs); |
| ValidateExpected("async3", CssLinkHref("x.css"), CssLinkHref(kRewrittenUrl)); |
| // Expired meta-data (3). expired HTTP cache for a.css and b.css, fresh in |
| // HTTP cache for x.css. |
| EXPECT_EQ(6, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| // Inserts for all meta-data, a.css and b.css in HTTPcache and rewritten URL |
| // in HTTP cache. |
| EXPECT_EQ(6, lru_cache()->num_inserts()); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(2, http_cache()->cache_expirations()->Get()); |
| // We log only for x.css metadata miss. |
| EXPECT_EQ(1, logging_info()->metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, logging_info()->metadata_cache_info().num_hits()); |
| ClearStats(); |
| } |
| |
| TEST_F(RewriteContextTest, Nested) { |
| const GoogleString kRewrittenUrl = Encode("", "nf", "0", "c.css", "css"); |
| InitNestedFilter(NestedFilter::kExpectNestedRewritesSucceed); |
| InitResources(); |
| ValidateExpected("async3", CssLinkHref("c.css"), CssLinkHref(kRewrittenUrl)); |
| GoogleString rewritten_contents; |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, kRewrittenUrl), |
| &rewritten_contents)); |
| // Note: These tests do not use HtmlResourceSlots and thus they do not |
| // preserve URL relativity. |
| EXPECT_EQ(StrCat(Encode(kTestDomain, "uc", "0", "a.css", "css"), "\n", |
| Encode(kTestDomain, "uc", "0", "b.css", "css"), "\n"), |
| rewritten_contents); |
| } |
| |
| TEST_F(RewriteContextTest, NestedFailed) { |
| // Make sure that the was_optimized() bit is not set when the nested |
| // rewrite fails (which it will since it's already all caps) |
| const GoogleString kRewrittenUrl = Encode("", "nf", "0", "t.css", "css"); |
| InitNestedFilter(NestedFilter::kExpectNestedRewritesFail); |
| InitResources(); |
| ResponseHeaders default_css_header; |
| SetDefaultLongCacheHeaders(&kContentTypeCss, &default_css_header); |
| SetFetchResponse("http://test.com/u.css", default_css_header, |
| "UPPERCASE"); |
| SetFetchResponse("http://test.com/t.css", default_css_header, |
| "u.css"); |
| ValidateExpected("nested-noop", CssLinkHref("t.css"), |
| CssLinkHref(kRewrittenUrl)); |
| } |
| |
| TEST_F(RewriteContextTest, NestedChained) { |
| const GoogleString kRewrittenUrl = Encode("", "nf", "0", "c.css", "css"); |
| |
| InitNestedFilter(NestedFilter::kExpectNestedRewritesSucceed); |
| nested_filter_->set_chain(true); |
| InitResources(); |
| ValidateExpected( |
| "async_nest_chain", CssLinkHref("c.css"), CssLinkHref(kRewrittenUrl)); |
| GoogleString rewritten_contents; |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, kRewrittenUrl), |
| &rewritten_contents)); |
| // We expect each URL twice since we have two nested jobs for it, and the |
| // Harvest() just dumps each nested rewrites' slots. |
| // Note: These tests do not use HtmlResourceSlots and thus they do not |
| // preserve URL relativity. |
| EXPECT_EQ(StrCat(Encode(kTestDomain, "uc", "0", "a.css", "css"), "\n", |
| Encode(kTestDomain, "uc", "0", "a.css", "css"), "\n", |
| Encode(kTestDomain, "uc", "0", "b.css", "css"), "\n", |
| Encode(kTestDomain, "uc", "0", "b.css", "css"), "\n"), |
| rewritten_contents); |
| } |
| |
| TEST_F(RewriteContextTest, Cancel) { |
| // Make sure Cancel is called properly when disable_further_processing() |
| // is invoked. |
| RewriteDriver* driver = rewrite_driver(); |
| CombiningFilter* combining_filter1 = |
| new CombiningFilter(driver, mock_scheduler(), 0 /* no delay*/); |
| CombiningFilter* combining_filter2 = |
| new CombiningFilter(driver, mock_scheduler(), 0 /* no delay*/); |
| driver->AppendRewriteFilter(combining_filter1); |
| driver->AppendRewriteFilter(combining_filter2); |
| server_context()->ComputeSignature(options()); |
| InitResources(); |
| GoogleString combined_url = |
| Encode("", CombiningFilter::kFilterId, "0", |
| MultiUrl("a.css", "b.css"), "css"); |
| |
| ValidateExpected("cancel", StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(0, combining_filter1->num_cancel()); |
| // Element getting deleted disables further processing. |
| EXPECT_EQ(1, combining_filter2->num_cancel()); |
| } |
| |
| TEST_F(RewriteContextTest, WillNotRewrite) { |
| // Make sure WillNotRewrite is called properly when filter misses the |
| // deadline. |
| RewriteDriver* driver = rewrite_driver(); |
| TrimWhitespaceRewriter* trimmer = |
| new TrimWhitespaceRewriter(kRewrittenResource); |
| rewrite_driver()->AppendRewriteFilter( |
| new SimpleTextFilter(trimmer, rewrite_driver())); |
| CombiningFilter* combining_filter = |
| new CombiningFilter(driver, mock_scheduler(), 100 /* delay, ms */); |
| driver->AppendRewriteFilter(combining_filter); |
| server_context()->ComputeSignature(options()); |
| |
| GoogleString out_url_a = Encode("", TrimWhitespaceRewriter::kFilterId, |
| "0", "a.css", "css"); |
| GoogleString out_url_c = Encode("", TrimWhitespaceRewriter::kFilterId, |
| "0", "c.css", "css"); |
| InitResources(); |
| ValidateExpected( |
| "will_not_rewrite", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("c.css")), |
| StrCat(CssLinkHref(out_url_a), CssLinkHref(out_url_c))); |
| EXPECT_EQ(0, combining_filter->num_render()); |
| EXPECT_EQ(1, combining_filter->num_will_not_render()); |
| } |
| |
| TEST_F(RewriteContextTest, RewritePartitionFailed) { |
| // PartitionFailed still calls Rewrite., as documented. |
| RewriteDriver* driver = rewrite_driver(); |
| CombiningFilter* combining_filter = |
| new CombiningFilter(driver, mock_scheduler(), 0 /* delay, ms */); |
| driver->AppendRewriteFilter(combining_filter); |
| server_context()->ComputeSignature(options()); |
| SetFetchResponse404("404.css"); |
| ValidateNoChanges("will_not_rewrite_partition_failed", |
| StrCat(CssLinkHref("404.css"), CssLinkHref("404.css"))); |
| EXPECT_EQ(1, combining_filter->num_render()); |
| EXPECT_EQ(0, combining_filter->num_will_not_render()); |
| } |
| |
| TEST_F(RewriteContextTest, DisableFurtherProcessing) { |
| // Make sure that set_disable_further_processing() done in the combiner |
| // prevents later rewrites from running. To test this, we add the combiner |
| // before the trimmer. |
| RewriteDriver* driver = rewrite_driver(); |
| CombiningFilter* combining_filter = |
| new CombiningFilter(driver, mock_scheduler(), 0 /* no delay */); |
| driver->AppendRewriteFilter(combining_filter); |
| TrimWhitespaceRewriter* trimmer = |
| new TrimWhitespaceRewriter(kRewrittenResource); |
| rewrite_driver()->AppendRewriteFilter( |
| new SimpleTextFilter(trimmer, rewrite_driver())); |
| driver->AddFilters(); |
| |
| InitResources(); |
| GoogleString combined_leaf = Encode("", CombiningFilter::kFilterId, "0", |
| MultiUrl("a.css", "b.css"), "css"); |
| GoogleString trimmed_url = Encode( |
| "", TrimWhitespaceRewriter::kFilterId, "0", combined_leaf, "css"); |
| ValidateExpected( |
| "combine_then_trim", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(trimmed_url)); |
| |
| // Should only be 1 rewrite: on the actual combined link, not the slot |
| // that used to have b.css. Note that this doesn't really cover |
| // disable_further_processing, since the framework may avoid |
| // the issue by reusing the rewrite. |
| EXPECT_EQ(1, trimmer->num_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(1, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(2, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(2, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(3, metadata_cache_info().num_rewrites_completed()); |
| ClearStats(); |
| |
| // Now prevent trim from running. Should not see it in the URL. |
| combining_filter->set_disable_successors(true); |
| GoogleString combined_url = Encode( |
| "", CombiningFilter::kFilterId, "0", MultiUrl("a.css", "b.css"), "css"); |
| ValidateExpected( |
| "combine_then_block_trim", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(combined_url)); |
| |
| EXPECT_EQ(1, trimmer->num_rewrites()); // unchanged. |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(2, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(3, metadata_cache_info().num_rewrites_completed()); |
| ClearStats(); |
| |
| // Cached, too. |
| ValidateExpected( |
| "combine_then_block_trim", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(combined_url)); |
| |
| EXPECT_EQ(1, trimmer->num_rewrites()); // unchanged. |
| EXPECT_EQ(0, metadata_cache_info().num_repeated_rewrites()); |
| EXPECT_EQ(2, metadata_cache_info().num_disabled_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(3, metadata_cache_info().num_rewrites_completed()); |
| ClearStats(); |
| } |
| |
| TEST_F(RewriteContextTest, CombinationRewrite) { |
| InitCombiningFilter(0); |
| EnableDebug(); |
| InitResources(); |
| GoogleString combined_url = Encode("", CombiningFilter::kFilterId, |
| "0", MultiUrl("a.css", "b.css"), "css"); |
| ValidateExpected( |
| "combination_rewrite", StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); // partition, and 2 inputs. |
| EXPECT_EQ(4, lru_cache()->num_inserts()); // partition, output, and 2 inputs. |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| ValidateExpected( |
| "combination_rewrite2", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // the output is all we need |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Proof-of-concept simulation of a Rewriter where delay is injected into |
| // the Rewrite flow. |
| TEST_F(RewriteContextTest, CombinationRewriteWithDelay) { |
| InitCombiningFilter(kRewriteDelayMs); |
| DebugWithMessage("<!--deadline_exceeded for filter Combining-->"); |
| InitResources(); |
| GoogleString combined_url = Encode("", CombiningFilter::kFilterId, |
| "0", MultiUrl("a.css", "b.css"), "css"); |
| ValidateExpected( |
| "xx", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| StrCat(CssLinkHref("a.css"), DebugMessage(""), |
| CssLinkHref("b.css"), DebugMessage(""))); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); // partition, and 2 inputs. |
| EXPECT_EQ(3, lru_cache()->num_inserts()); // partition+2 in, output not ready |
| ClearStats(); |
| |
| // The delay was too large so we were not able to complete the |
| // Rewrite. Now give it more time so it will complete. |
| |
| rewrite_driver()->BoundedWaitFor( |
| RewriteDriver::kWaitForCompletion, kRewriteDelayMs); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(1, lru_cache()->num_inserts()); // finally we cache the output. |
| ClearStats(); |
| |
| ValidateExpected( |
| "combination_rewrite", StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); // partition, and 2 inputs. |
| EXPECT_EQ(0, lru_cache()->num_inserts()); // partition, output, and 2 inputs. |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| ClearStats(); |
| |
| ValidateExpected( |
| "combination_rewrite2", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // the output is all we need |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // This is the same test as the first stanza of CombinationRewriteWithDelay, but |
| // includes the Debug filter so we get DeadlineExceeded debug messages injected. |
| TEST_F(RewriteContextTest, CombinationRewriteWithDelayAndDebug) { |
| InitCombiningFilter(kRewriteDelayMs); |
| EnableDebug(); |
| InitResources(); |
| Parse("xx", StrCat(CssLinkHref("a.css"), CssLinkHref("b.css"))); |
| GoogleString kDeadlineExceededComment(StrCat( |
| "<!--", RewriteDriver::DeadlineExceededMessage("Combining"), "-->")); |
| EXPECT_TRUE(output_buffer_.find( |
| StrCat(CssLinkHref("a.css"), kDeadlineExceededComment, |
| CssLinkHref("b.css"), kDeadlineExceededComment)) |
| != GoogleString::npos); |
| } |
| |
| TEST_F(RewriteContextTest, CombinationFetch) { |
| InitCombiningFilter(0); |
| EnableDebug(); |
| InitResources(); |
| |
| GoogleString combined_url = Encode(kTestDomain, CombiningFilter::kFilterId, |
| "0", MultiUrl("a.css", "b.css"), "css"); |
| |
| // The input URLs are not in cache, but the fetch should work. |
| GoogleString content; |
| EXPECT_TRUE(FetchResourceUrl(combined_url, &content)); |
| EXPECT_EQ(" a b", content); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(4, lru_cache()->num_misses()) |
| << "1 miss for the output. 1 before we acquire the lock, " |
| << "and one after we acquire the lock. Then we miss on the metadata " |
| << "and the two inputs."; |
| |
| EXPECT_EQ(4, lru_cache()->num_inserts()) << "2 inputs, 1 output, 1 metadata."; |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| Variable* v = statistics()->GetVariable( |
| RewriteContext::kNumDeadlineAlarmInvocations); |
| EXPECT_EQ(0, v->Get()); |
| ClearStats(); |
| content.clear(); |
| |
| // Now fetch it again. This time the output resource is cached. |
| EXPECT_TRUE(FetchResourceUrl(combined_url, &content)); |
| EXPECT_EQ(" a b", content); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // FYI: Takes ~70000 ms to run under Valgrind. |
| TEST_F(RewriteContextTest, FetchDeadlineTest) { |
| // This tests that deadlines on fetches are functional. |
| // This uses a combining filter with one input, as it has the needed delay |
| // functionality. |
| InitCombiningFilter(Timer::kMonthMs); |
| EnableDebug(); |
| InitResources(); |
| combining_filter_->set_prefix("|"); |
| |
| GoogleString combined_url = Encode(kTestDomain, CombiningFilter::kFilterId, |
| "0", "a.css", "css"); |
| |
| GoogleString content; |
| EXPECT_TRUE(FetchResourceUrl(combined_url, &content)); |
| // Should not get a |, as 1 month is way bigger than the rendering deadline. |
| EXPECT_EQ(" a ", content); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); // input, output, metadata |
| |
| // However, due to mock scheduler auto-advance, it should finish |
| // everything now, and be able to do it from cache. |
| content.clear(); |
| Variable* v = statistics()->GetVariable( |
| RewriteContext::kNumDeadlineAlarmInvocations); |
| EXPECT_EQ(1, v->Get()); |
| |
| ClearStats(); |
| EXPECT_TRUE(FetchResourceUrl(combined_url, &content)); |
| EXPECT_EQ("| a ", content); |
| |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, FetchDeadlineMandatoryTest) { |
| // Version of FetchDeadlineTest where the filter is marked as not being |
| // an optimization only. This effectively disables the deadline. |
| InitCombiningFilter(Timer::kMonthMs); |
| EnableDebug(); |
| InitResources(); |
| combining_filter_->set_optimization_only(false); |
| combining_filter_->set_prefix("|"); |
| |
| GoogleString combined_url = Encode(kTestDomain, CombiningFilter::kFilterId, |
| "0", "a.css", "css"); |
| |
| GoogleString content; |
| EXPECT_TRUE(FetchResourceUrl(combined_url, &content)); |
| // Should get a |, despite 1 month simulated delay inside the combine filter |
| // being way bigger than the rendering deadline. |
| EXPECT_EQ("| a ", content); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); // input, output, metadata |
| } |
| |
| TEST_F(RewriteContextTest, FetchDeadlineTestBeforeDeadline) { |
| // As above, but rewrite finishes quickly. This time we should see the | |
| // immediately |
| InitCombiningFilter(1 /*ms*/); |
| EnableDebug(); |
| InitResources(); |
| combining_filter_->set_prefix("|"); |
| |
| GoogleString combined_url = Encode(kTestDomain, CombiningFilter::kFilterId, |
| "0", "a.css", "css"); |
| |
| GoogleString content; |
| EXPECT_TRUE(FetchResourceUrl(combined_url, &content)); |
| // Should get a |, as 1 ms is smaller than the rendering deadline. |
| EXPECT_EQ("| a ", content); |
| |
| // And of course it's nicely cached. |
| content.clear(); |
| ClearStats(); |
| EXPECT_TRUE(FetchResourceUrl(combined_url, &content)); |
| EXPECT_EQ("| a ", content); |
| |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, LoadSheddingTest) { |
| const int kThresh = 20; |
| server_context()->low_priority_rewrite_workers()-> |
| SetLoadSheddingThreshold(kThresh); |
| |
| const char kCss[] = " * { display: none; } "; |
| const char kMinifiedCss[] = "*{display:none}"; |
| |
| InitResources(); |
| for (int i = 0; i < 2 * kThresh; ++i) { |
| GoogleString file_name = IntegerToString(i); |
| SetResponseWithDefaultHeaders( |
| file_name, kContentTypeCss, kCss, Timer::kYearMs / Timer::kSecondMs); |
| } |
| |
| // We use a sync point here to wedge the combining filter, and then have |
| // other filters behind it accumulate lots of work and get load-shed. |
| InitCombiningFilter(0); |
| EnableDebug(); |
| combining_filter_->set_prefix("|"); |
| WorkerTestBase::SyncPoint rewrite_reached( |
| server_context()->thread_system()); |
| WorkerTestBase::SyncPoint resume_rewrite(server_context()->thread_system()); |
| combining_filter_->set_rewrite_signal_on(&rewrite_reached); |
| combining_filter_->set_rewrite_block_on(&resume_rewrite); |
| |
| GoogleString combined_url = Encode(kTestDomain, CombiningFilter::kFilterId, |
| "0", "a.css", "css"); |
| |
| GoogleString out_combine; |
| StringAsyncFetch async_fetch(CreateRequestContext(), &out_combine); |
| rewrite_driver()->FetchResource(combined_url, &async_fetch); |
| rewrite_reached.Wait(); |
| |
| // We need separate rewrite drivers, strings, and callbacks for each of the |
| // other requests.. |
| std::vector<GoogleString*> outputs; |
| std::vector<StringAsyncFetch*> fetchers; |
| std::vector<RewriteDriver*> drivers; |
| |
| for (int i = 0; i < 2 * kThresh; ++i) { |
| GoogleString file_name = IntegerToString(i); |
| GoogleString* out = new GoogleString; |
| RequestContextPtr ctx = |
| RequestContext::NewTestRequestContext( |
| server_context()->thread_system()); |
| StringAsyncFetch* fetch = new StringAsyncFetch(ctx, out); |
| RewriteDriver* driver = server_context()->NewRewriteDriver(ctx); |
| GoogleString out_url = |
| Encode(kTestDomain, "cf", "0", file_name, "css"); |
| driver->FetchResource(out_url, fetch); |
| |
| outputs.push_back(out); |
| fetchers.push_back(fetch); |
| drivers.push_back(driver); |
| } |
| |
| // Note that we know that we're stuck in the middle of combining filter's |
| // rewrite, as it signaled us on rewrite_reached, but we didn't yet |
| // signal on resume_rewrite. This means that once the 2 * kThresh rewrites |
| // will get queued up, we will be forced to load-shed kThresh of them |
| // (with combiner not canceled since it's already "running"), and so the |
| // rewrites 0 ... kThresh - 1 can actually complete via shedding now. |
| for (int i = 0; i < kThresh; ++i) { |
| drivers[i]->WaitForCompletion(); |
| drivers[i]->Cleanup(); |
| // Since this got load-shed, we expect output to be unoptimized, |
| // and private cache-control. |
| EXPECT_STREQ(kCss, *outputs[i]) << "rewrite:" << i; |
| EXPECT_TRUE(fetchers[i]->response_headers()->HasValue( |
| HttpAttributes::kCacheControl, "private")); |
| } |
| |
| // Unwedge the combiner, then collect other rewrites. |
| resume_rewrite.Notify(); |
| |
| for (int i = kThresh; i < 2 * kThresh; ++i) { |
| drivers[i]->WaitForCompletion(); |
| drivers[i]->Cleanup(); |
| // These should be optimized. |
| EXPECT_STREQ(kMinifiedCss, *outputs[i]) << "rewrite:" << i; |
| EXPECT_FALSE(fetchers[i]->response_headers()->HasValue( |
| HttpAttributes::kCacheControl, "private")); |
| } |
| |
| STLDeleteElements(&outputs); |
| STLDeleteElements(&fetchers); |
| |
| rewrite_driver()->WaitForShutDown(); |
| } |
| |
| TEST_F(RewriteContextTest, CombinationFetchMissing) { |
| InitCombiningFilter(0); |
| EnableDebug(); |
| SetFetchFailOnUnexpected(false); |
| GoogleString combined_url = Encode(kTestDomain, CombiningFilter::kFilterId, |
| "0", MultiUrl("a.css", "b.css"), "css"); |
| EXPECT_FALSE(TryFetchResource(combined_url)); |
| } |
| |
| TEST_F(RewriteContextTest, CombinationFetchNestedMalformed) { |
| // Fetch of a combination where nested URLs look like they were pagespeed- |
| // produced, but actually have invalid filter ids. |
| InitCombiningFilter(0); |
| EnableDebug(); |
| SetFetchFailOnUnexpected(false); |
| GoogleString combined_url = Encode( |
| kTestDomain, CombiningFilter::kFilterId, "0", |
| MultiUrl("a.pagespeed.nosuchfilter.0.css", |
| "b.pagespeed.nosuchfilter.0.css"), "css"); |
| EXPECT_FALSE(TryFetchResource(combined_url)); |
| } |
| |
| TEST_F(RewriteContextTest, CombinationFetchSeedsCache) { |
| // Make sure that fetching a combination seeds cache for future rewrites |
| // properly. |
| InitCombiningFilter(0 /* no rewrite delay*/); |
| EnableDebug(); |
| InitResources(); |
| |
| // First fetch it.. |
| GoogleString combined_url = Encode("", CombiningFilter::kFilterId, |
| "0", MultiUrl("a.css", "b.css"), "css"); |
| GoogleString content; |
| EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, combined_url), &content)); |
| EXPECT_EQ(" a b", content); |
| ClearStats(); |
| |
| // Then use from HTML. |
| ValidateExpected( |
| "hopefully_hashed", |
| StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Test that rewriting works correctly when input resource is loaded from disk. |
| |
| TEST_F(RewriteContextTest, LoadFromFileOnTheFly) { |
| options()->file_load_policy()->Associate(kTestDomain, "/test/"); |
| InitTrimFilters(kOnTheFlyResource); |
| |
| // Init file resources. |
| WriteFile("/test/a.css", " foo b ar "); |
| |
| // The first rewrite was successful because we block for reading from |
| // filesystem, not because we did any cache lookups. |
| ClearStats(); |
| ValidateExpected("trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| // 1 cache miss for the OutputPartitions. The input resource does not |
| // induce a cache check as it's loaded from the file system. |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| // 1 cache insertion: resource mapping (CachedResult). |
| // Output resource not stored in cache (because it's an on-the-fly resource). |
| EXPECT_EQ(1, lru_cache()->num_inserts()); |
| // No fetches because it's loaded from file. |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, file_system()->num_input_file_opens()); |
| |
| // The second cache time we request this URL, we should find no additional |
| // cache inserts or fetches. The rewrite should complete using a single |
| // cache hit for the metadata. No cache misses will occur. |
| ClearStats(); |
| ValidateExpected("trimmable", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, file_system()->num_input_file_opens()); |
| // Note: We do not load the resource again until the fetch. |
| } |
| |
| TEST_F(RewriteContextTest, LoadFromFileRewritten) { |
| options()->file_load_policy()->Associate(kTestDomain, "/test/"); |
| InitTrimFilters(kRewrittenResource); |
| |
| // Init file resources. |
| WriteFile("/test/a.css", " foo b ar "); |
| |
| // The first rewrite was successful because we block for reading from |
| // filesystem, not because we did any cache lookups. |
| ClearStats(); |
| ValidateExpected("trimmable", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| // 1 cache miss for the OutputPartitions. No cache lookup is |
| // done for the input resource since it is loaded from the file system. |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| // 2 cache insertion: resource mapping (CachedResult) and output resource. |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| // No fetches because it's loaded from file. |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, file_system()->num_input_file_opens()); |
| |
| // The second cache time we request this URL, we should find no additional |
| // cache inserts or fetches. The rewrite should complete using a single |
| // cache hit for the metadata. No cache misses will occur. |
| ClearStats(); |
| ValidateExpected("trimmable", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, file_system()->num_input_file_opens()); |
| // Note: We do not load the resource again until the fetch. |
| } |
| |
| namespace { |
| |
| // Filter that blocks on Flush() in order to let an actual rewrite succeed |
| // while we are still 'parsing'. |
| class TestWaitFilter : public CommonFilter { |
| public: |
| TestWaitFilter(RewriteDriver* driver, |
| WorkerTestBase::SyncPoint* sync) |
| : CommonFilter(driver), sync_(sync) {} |
| virtual ~TestWaitFilter() {} |
| |
| virtual const char* Name() const { return "TestWait"; } |
| virtual void StartDocumentImpl() {} |
| virtual void StartElementImpl(net_instaweb::HtmlElement*) {} |
| virtual void EndElementImpl(net_instaweb::HtmlElement*) {} |
| |
| virtual void Flush() { |
| sync_->Wait(); |
| driver()->set_externally_managed(true); |
| CommonFilter::Flush(); |
| } |
| |
| private: |
| WorkerTestBase::SyncPoint* sync_; |
| DISALLOW_COPY_AND_ASSIGN(TestWaitFilter); |
| }; |
| |
| // Filter that wakes up a given sync point once its rewrite context is |
| // getting destroyed. |
| class TestNotifyFilter : public CommonFilter { |
| public: |
| class Context : public SingleRewriteContext { |
| public: |
| Context(RewriteDriver* driver, WorkerTestBase::SyncPoint* sync) |
| : SingleRewriteContext(driver, NULL /* parent */, |
| NULL /* resource context*/), |
| sync_(sync) {} |
| |
| virtual ~Context() { |
| sync_->Notify(); |
| } |
| |
| protected: |
| virtual void RewriteSingle( |
| const ResourcePtr& input, const OutputResourcePtr& output) { |
| RewriteDone(kRewriteFailed, 0); |
| } |
| |
| virtual const char* id() const { return "testnotify"; } |
| virtual OutputResourceKind kind() const { return kRewrittenResource; } |
| |
| private: |
| WorkerTestBase::SyncPoint* sync_; |
| DISALLOW_COPY_AND_ASSIGN(Context); |
| }; |
| |
| TestNotifyFilter(RewriteDriver* driver, WorkerTestBase::SyncPoint* sync) |
| : CommonFilter(driver), sync_(sync) {} |
| virtual ~TestNotifyFilter() {} |
| |
| virtual const char* Name() const { |
| return "Notify"; |
| } |
| |
| virtual void StartDocumentImpl() {} |
| virtual void StartElementImpl(net_instaweb::HtmlElement* element) { |
| HtmlElement::Attribute* href = element->FindAttribute(HtmlName::kHref); |
| if (href != NULL) { |
| bool unused; |
| ResourcePtr input_resource(CreateInputResource( |
| href->DecodedValueOrNull(), &unused)); |
| ResourceSlotPtr slot(driver()->GetSlot(input_resource, element, href)); |
| Context* context = new Context(driver(), sync_); |
| context->AddSlot(slot); |
| driver()->InitiateRewrite(context); |
| } |
| } |
| |
| virtual void EndElementImpl(net_instaweb::HtmlElement*) {} |
| |
| private: |
| WorkerTestBase::SyncPoint* sync_; |
| DISALLOW_COPY_AND_ASSIGN(TestNotifyFilter); |
| }; |
| |
| } // namespace |
| |
| // Test to make sure we don't crash/delete a RewriteContext when it's completed |
| // while we're still writing. Not 100% guaranteed to crash, however, as |
| // we notice in ~TestNotifyFilter::Context and not when context is fully |
| // destroyed. |
| TEST_F(RewriteContextTest, UltraQuickRewrite) { |
| // Turn on automatic memory management for now, to see if it tries to |
| // auto-delete while still parsing. We turn it off inside |
| // TestWaitFilter::Flush. |
| rewrite_driver()->set_externally_managed(false); |
| InitResources(); |
| |
| WorkerTestBase::SyncPoint sync(server_context()->thread_system()); |
| rewrite_driver()->AppendOwnedPreRenderFilter( |
| new TestNotifyFilter(rewrite_driver(), &sync)); |
| rewrite_driver()->AddOwnedPostRenderFilter( |
| new TestWaitFilter(rewrite_driver(), &sync)); |
| server_context()->ComputeSignature(options()); |
| |
| ValidateExpected("trimmable.quick", CssLinkHref("a.css"), |
| CssLinkHref("a.css")); |
| } |
| |
| TEST_F(RewriteContextTest, RenderCompletesCacheAsync) { |
| // Make sure we finish rendering fully even when cache is ultra-slow. |
| SetCacheDelayUs(50 * kRewriteDeadlineMs * 1000); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| // First time we're fetching, so we don't know. |
| Parse("trimmable_async", CssLinkHref("a.css")); |
| rewrite_driver()->WaitForCompletion(); |
| |
| ValidateExpected("trimmable_async", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| } |
| |
| TEST_F(RewriteContextTest, TestDisableBackgroundRewritesForBots) { |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| options()->ClearSignatureForTesting(); |
| options()->set_disable_background_fetches_for_bots(true); |
| options()->ComputeSignature(); |
| |
| // Bot user agent. No fetches triggered. |
| ResetUserAgent(UserAgentMatcherTestBase::kGooglebotUserAgent); |
| ValidateNoChanges("initial", CssLinkHref("a.css")); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Non-bot user agent. Fetch and rewrite triggered. |
| ResetUserAgent("new"); |
| ValidateExpected( |
| "initial", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(3, counting_url_async_fetcher()->byte_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Bot user agent. HTML is rewritten. |
| ResetUserAgent(UserAgentMatcherTestBase::kGooglebotUserAgent); |
| ValidateExpected( |
| "initial", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| |
| // Advance close to expiry, so that freshen is triggered. |
| AdvanceTimeMs(kOriginTtlMs * 9 / 10); |
| |
| ClearStats(); |
| // Bot user agent. HTML is rewritten, but no fetches are triggered. |
| ResetUserAgent(UserAgentMatcherTestBase::kGooglebotUserAgent); |
| ValidateExpected( |
| "initial", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Non-bot user agent. Freshen triggers a fetch. |
| ResetUserAgent("new"); |
| ValidateExpected( |
| "initial", |
| CssLinkHref("a.css"), |
| CssLinkHref(Encode("", "tw", "0", "a.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| |
| // Advance beyond expiry. |
| AdvanceTimeMs(kOriginTtlMs * 2); |
| |
| ClearStats(); |
| // Bot user agent. No fetches are triggered. |
| ResetUserAgent(UserAgentMatcherTestBase::kGooglebotUserAgent); |
| ValidateNoChanges("initial", CssLinkHref("a.css")); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(3, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| } |
| |
| TEST_F(RewriteContextTest, TestFreshen) { |
| FetcherUpdateDateHeaders(); |
| |
| // Note that this must be >= kDefaultImplicitCacheTtlMs for freshening. |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs * 10; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| |
| // Start with non-zero time, and init our resource.. |
| AdvanceTimeMs(kTtlMs / 2); |
| InitTrimFilters(kRewrittenResource); |
| |
| ResponseHeaders response_headers; |
| response_headers.Add(HttpAttributes::kContentType, |
| kContentTypeCss.mime_type()); |
| response_headers.SetDateAndCaching(timer()->NowMs(), kTtlMs); |
| response_headers.Add(HttpAttributes::kEtag, "etag"); |
| response_headers.SetStatusAndReason(HttpStatus::kOK); |
| response_headers.ComputeCaching(); |
| mock_url_fetcher()->SetConditionalResponse( |
| "http://test.com/test.css", -1, "etag", response_headers, kDataIn); |
| |
| // First fetch + rewrite |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| // Note that this only measures the number of bytes in the response body. |
| EXPECT_EQ(9, counting_url_async_fetcher()->byte_count()); |
| // Cache miss for the original. The original and rewritten resource, as well |
| // as the metadata are inserted into cache. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Advance halfway from TTL. This should be an entire cache hit. |
| AdvanceTimeMs(kTtlMs / 2); |
| ValidateExpected("fully_hit", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| // No HTTPCache lookups or writes. One metadata cache hit while rewriting. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| response_headers.FixDateHeaders(timer()->NowMs()); |
| // Advance close to TTL and rewrite. We should see an extra fetch. |
| // Also upload a version with a newer timestamp. |
| mock_url_fetcher()->SetConditionalResponse( |
| "http://test.com/test.css", -1, "etag", response_headers, kDataIn); |
| AdvanceTimeMs(kTtlMs / 2 - 3 * Timer::kMinuteMs); |
| ValidateExpected("freshen", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ( |
| 1, |
| server_context()->rewrite_stats()->num_conditional_refreshes()->Get()); |
| // No bytes are downloaded since we conditionally refresh the resource. |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| // Miss for the original since it is within a minute of its expiration time. |
| // The newly fetched resource is inserted into the cache, and the metadata is |
| // updated. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Advance again closer to the TTL. This shouldn't trigger any fetches since |
| // the last freshen updated the cache. Also, no freshens are triggered here |
| // since the last freshen updated the metadata cache. |
| AdvanceTimeMs(2 * Timer::kMinuteMs); |
| ValidateExpected("freshen", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| |
| ClearStats(); |
| // Now advance past original expiration. Note that we don't require any extra |
| // fetches since the resource was freshened by the previous fetch. |
| SetupWaitFetcher(); |
| AdvanceTimeMs(kTtlMs * 4 / 10); |
| ValidateExpected("freshen2", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| // Make sure we do this or it will leak. |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| } |
| |
| TEST_F(RewriteContextTest, TestFreshenForMultipleResourceRewrites) { |
| FetcherUpdateDateHeaders(); |
| InitCombiningFilter(0 /* no rewrite delay */); |
| EnableDebug(); |
| // We use MD5 hasher instead of mock hasher so that the rewritten url changes |
| // when its content gets updated. |
| UseMd5Hasher(); |
| |
| // Note that this must be >= kDefaultImplicitCacheTtlMs for freshening. |
| const int kTtlMs1 = RewriteOptions::kDefaultImplicitCacheTtlMs * 10; |
| const char kPath1[] = "first.css"; |
| const char kDataIn1[] = " first "; |
| const char kDataNew1[] = " new first "; |
| |
| const int kTtlMs2 = RewriteOptions::kDefaultImplicitCacheTtlMs * 5; |
| const char kPath2[] = "second.css"; |
| const char kDataIn2[] = " second "; |
| |
| // Start with non-zero time, and init our resources. |
| AdvanceTimeMs(kTtlMs2 / 2); |
| SetResponseWithDefaultHeaders(kPath1, kContentTypeCss, kDataIn1, |
| kTtlMs1 / Timer::kSecondMs); |
| SetResponseWithDefaultHeaders(kPath2, kContentTypeCss, kDataIn2, |
| kTtlMs2 / Timer::kSecondMs); |
| |
| // First fetch + rewrite |
| GoogleString combined_url = Encode( |
| "", CombiningFilter::kFilterId, "V3iNJlBg52", |
| MultiUrl("first.css", "second.css"), "css"); |
| |
| ValidateExpected("initial", |
| StrCat(CssLinkHref("first.css"), CssLinkHref("second.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(1, combining_filter_->num_rewrites()); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| // Cache misses for both the css files. The original resources, the combined |
| // css file and the metadata is inserted into cache. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(3, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(4, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Advance halfway from TTL. This should be an entire cache hit. |
| AdvanceTimeMs(kTtlMs2 / 2); |
| ValidateExpected("fully_hit", |
| StrCat(CssLinkHref("first.css"), CssLinkHref("second.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(0, combining_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| // No HTTPCache lookups or writes. One metadata cache hit while rewriting. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Advance close to TTL and rewrite. We should see an extra fetch. |
| // Also upload a version with a newer timestamp. |
| SetResponseWithDefaultHeaders(kPath2, kContentTypeCss, kDataNew1, |
| kTtlMs2 / Timer::kSecondMs); |
| AdvanceTimeMs(kTtlMs2 / 2 - 3 * Timer::kMinuteMs); |
| |
| // Grab a lock for the resource that we are trying to freshen, preventing |
| // that flow from working. |
| scoped_ptr<NamedLock> lock(MakeInputLock(StrCat(kTestDomain, kPath2))); |
| NamedLockTester lock_tester(server_context()->thread_system()); |
| ASSERT_TRUE(lock_tester.TryLock(lock.get())); |
| ValidateExpected("freshen", |
| StrCat(CssLinkHref("first.css"), CssLinkHref("second.css")), |
| CssLinkHref(combined_url)); |
| |
| // We do the cache lookups before acquiring the lock. Based on the TTL in |
| // the resource, we decide we want to freshen, we attempt to grab the lock |
| // to initiate a new fetch, but fail. No fetch is made, metadata cache is |
| // not cleared. Nothing is inserted into the cache. |
| EXPECT_EQ(0, combining_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()) << "metadata&soon-to-expire second.css"; |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, lru_cache()->num_deletes()); |
| lock->Unlock(); |
| |
| ClearStats(); |
| ValidateExpected("freshen", |
| StrCat(CssLinkHref("first.css"), CssLinkHref("second.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(0, combining_filter_->num_rewrites()); |
| // One fetch while freshening the second resource. |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| // Cache miss for the original since it is within a minute of its expiration |
| // time. The newly fetched resource is inserted into the cache. The metadata |
| // is deleted since one of the resources changed. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(1, lru_cache()->num_inserts()); |
| // Two deletes. One for the metadata. The replacement of the second resource |
| // in the HTTPCache is counted both as a delete, and an insert. |
| EXPECT_EQ(2, lru_cache()->num_deletes()); |
| |
| ClearStats(); |
| // Advance again closer to the TTL. This shouldn't trigger any fetches since |
| // the last freshen updated the cache. |
| AdvanceTimeMs(2 * Timer::kMinuteMs); |
| |
| combined_url = Encode("", CombiningFilter::kFilterId, "YosxgdTZiZ", |
| MultiUrl("first.css", "second.css"), "css"); |
| |
| ValidateExpected("freshen", |
| StrCat(CssLinkHref("first.css"), CssLinkHref("second.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(1, combining_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(2, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| |
| ClearStats(); |
| // Now advance past original expiration. Note that we don't require any extra |
| // fetches since the resource was freshened by the previous fetch. |
| SetupWaitFetcher(); |
| AdvanceTimeMs(kTtlMs2 * 4 / 10); |
| ValidateExpected("freshen2", |
| StrCat(CssLinkHref("first.css"), CssLinkHref("second.css")), |
| CssLinkHref(combined_url)); |
| // Make sure we do this or it will leak. |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, combining_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| } |
| |
| TEST_F(RewriteContextTest, TestFreshenForLowTtl) { |
| FetcherUpdateDateHeaders(); |
| |
| // Note that this must be >= kDefaultImplicitCacheTtlMs for freshening. |
| const int kTtlMs = 400 * Timer::kSecondMs; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| |
| // Start with non-zero time, and init our resource.. |
| AdvanceTimeMs(kTtlMs / 2); |
| InitTrimFilters(kRewrittenResource); |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| |
| // First fetch + rewrite |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| // Cache miss for the original. Both original and rewritten are inserted into |
| // cache. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| |
| ClearStats(); |
| // Advance halfway from TTL. This should be an entire cache hit. |
| AdvanceTimeMs(kTtlMs / 2); |
| ValidateExpected("fully_hit", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| // No HTTPCache lookups or writes. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| |
| ClearStats(); |
| // Advance close to TTL and rewrite. We should see an extra fetch. |
| // Also upload a version with a newer timestamp. |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| // Move to 85% of expiry. |
| AdvanceTimeMs((kTtlMs * 7) / 20); |
| ValidateExpected("freshen", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| // Miss for the original since it is within a minute of its expiration time. |
| // The newly fetched resource is inserted into the cache. The updated |
| // metadata is also inserted into cache. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Advance again closer to the TTL. This shouldn't trigger any fetches since |
| // the last freshen updated the cache. |
| // Move to 95% of expiry. |
| AdvanceTimeMs(kTtlMs / 10); |
| ValidateExpected("freshen", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| // We don't freshen again here since the last freshen updated the cache. |
| // One metadata cache hit while rewriting. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Advance past expiry. |
| AdvanceTimeMs(kTtlMs * 2); |
| SetupWaitFetcher(); |
| ValidateNoChanges("freshen", CssLinkHref(kPath)); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| // We don't rewrite here since the metadata expired. We revalidate the |
| // metadata, and insert the newly fetched resource and updated metadata into |
| // the cache. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| } |
| |
| TEST_F(RewriteContextTest, TestFreshenWithTwoLevelCache) { |
| // Note that this must be >= kDefaultImplicitCacheTtlMs for freshening. |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs * 10; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| |
| // Set up a 2-level cache |
| LRUCache l2_cache(1000); |
| WriteThroughCache write_through(lru_cache(), &l2_cache); |
| HTTPCache* two_level_cache = new HTTPCache( |
| &write_through, timer(), hasher(), statistics()); |
| two_level_cache->set_cache_levels(2); |
| server_context()->set_http_cache(two_level_cache); |
| |
| // Start with non-zero time, and init our resource. |
| AdvanceTimeMs(kTtlMs / 2); |
| InitTrimFilters(kRewrittenResource); |
| ResponseHeaders response_headers; |
| response_headers.Add(HttpAttributes::kContentType, |
| kContentTypeCss.mime_type()); |
| response_headers.SetDateAndCaching(timer()->NowMs(), kTtlMs); |
| response_headers.Add(HttpAttributes::kEtag, "etag"); |
| response_headers.SetStatusAndReason(HttpStatus::kOK); |
| response_headers.ComputeCaching(); |
| mock_url_fetcher()->SetConditionalResponse( |
| "http://test.com/test.css", -1, "etag", response_headers, kDataIn); |
| |
| // First fetch + rewrite. |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(9, counting_url_async_fetcher()->byte_count()); |
| // Cache miss for the original. Both original and rewritten are inserted into |
| // cache. Besides this, the metadata lookup fails and new metadata is inserted |
| // into cache. Note that the metadata cache is L1 only. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, l2_cache.num_hits()); |
| EXPECT_EQ(1, l2_cache.num_misses()); |
| EXPECT_EQ(2, l2_cache.num_inserts()); |
| |
| ClearStats(); |
| l2_cache.ClearStats(); |
| // Advance halfway from TTL. This should be an entire cache hit. |
| AdvanceTimeMs(kTtlMs / 2); |
| ValidateExpected("fully_hit", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| // L1 cache hit for the metadata. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, l2_cache.num_hits()); |
| EXPECT_EQ(0, l2_cache.num_misses()); |
| EXPECT_EQ(0, l2_cache.num_inserts()); |
| |
| // Create a new fresh response and insert into the L2 cache. Do this by |
| // creating a temporary HTTPCache with the L2 cache since we don't want to |
| // alter the state of the L1 cache whose response is no longer fresh. |
| response_headers.FixDateHeaders(timer()->NowMs()); |
| mock_url_fetcher()->SetConditionalResponse( |
| "http://test.com/test.css", -1, "etag", response_headers, kDataIn); |
| HTTPCache* l2_only_cache = new HTTPCache(&l2_cache, timer(), hasher(), |
| statistics()); |
| l2_only_cache->Put( |
| AbsolutifyUrl(kPath), rewrite_driver_->CacheFragment(), |
| RequestHeaders::Properties(), |
| ResponseHeaders::GetVaryOption(options()->respect_vary()), |
| &response_headers, kDataIn, message_handler()); |
| delete l2_only_cache; |
| |
| ClearStats(); |
| l2_cache.ClearStats(); |
| // Advance close to TTL and rewrite. No extra fetches here since we find the |
| // response in the L2 cache. |
| AdvanceTimeMs(kTtlMs / 2 - 30 * Timer::kSecondMs); |
| ValidateExpected("freshen", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| // We find a fresh response in the L2 cache and insert it into the L1 cache. |
| // We also update the metadata. |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, l2_cache.num_hits()); |
| EXPECT_EQ(0, l2_cache.num_misses()); |
| EXPECT_EQ(0, l2_cache.num_inserts()); |
| |
| ClearStats(); |
| l2_cache.ClearStats(); |
| // Advance again closer to the TTL. This shouldn't trigger any fetches since |
| // the last freshen updated the metadata. |
| AdvanceTimeMs(15 * Timer::kSecondMs); |
| ValidateExpected("freshen", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| // L1 cache hit for the metadata. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, l2_cache.num_hits()); |
| EXPECT_EQ(0, l2_cache.num_misses()); |
| EXPECT_EQ(0, l2_cache.num_inserts()); |
| |
| ClearStats(); |
| l2_cache.ClearStats(); |
| // Now, advance past original expiration. Since the metadata has expired we go |
| // through the OutputCacheRevalidate flow which looks up cache and finds that |
| // the result in cache is valid and calls OutputCacheDone resulting in a |
| // successful rewrite. Note that it also sees that the resource is close to |
| // expiry and triggers a freshen. The OutputCacheHit flow then triggers |
| // another freshen since it observes that the resource refernced in its |
| // metadata is close to expiry. |
| // Note that only one of these freshens actually trigger a fetch because of |
| // the locking mechanism in UrlInputResource to prevent parallel fetches of |
| // the same resource. |
| // As we are also reusing rewrite results when contents did not change, |
| // there is no second rewrite. |
| SetupWaitFetcher(); |
| AdvanceTimeMs(kTtlMs / 2 - 30 * Timer::kSecondMs); |
| ValidateExpected("freshen2", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| // The original resource gets refetched and inserted into cache. |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| EXPECT_EQ( |
| 1, |
| server_context()->rewrite_stats()->num_conditional_refreshes()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| // The entries in both the caches are not within the freshness threshold, and |
| // are hence counted as misses. |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, l2_cache.num_hits()); |
| EXPECT_EQ(0, l2_cache.num_misses()); |
| EXPECT_EQ(1, l2_cache.num_inserts()); |
| } |
| |
| TEST_F(RewriteContextTest, TestFreshenForExtendCache) { |
| FetcherUpdateDateHeaders(); |
| UseMd5Hasher(); |
| |
| // Note that this must be >= kDefaultImplicitCacheTtlMs for freshening. |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs * 10; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| const char kHash[] = "mmVFI7stDo"; |
| |
| // Start with non-zero time, and init our resource.. |
| AdvanceTimeMs(kTtlMs / 2); |
| options()->EnableFilter(RewriteOptions::kExtendCacheCss); |
| rewrite_driver()->AddFilters(); |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| |
| // First fetch + rewrite |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "ce", kHash, "test.css", "css"))); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| // Cache miss for the original. The original resource and the metadata is |
| // inserted into cache. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Advance halfway from TTL. This should be an entire cache hit. |
| AdvanceTimeMs(kTtlMs / 2); |
| ValidateExpected("fully_hit", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "ce", kHash, "test.css", "css"))); |
| EXPECT_EQ(1, statistics()->GetVariable("cache_extensions")->Get()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| // No HTTPCache lookups or writes. One metadata cache hit while rewriting. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| SetupWaitFetcher(); |
| // Advance close to TTL and rewrite. We should see an extra fetch. |
| // Also upload a version with a newer timestamp. |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| AdvanceTimeMs(kTtlMs / 2 - 3 * Timer::kMinuteMs); |
| ValidateExpected("freshen", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "ce", kHash, "test.css", "css"))); |
| CallFetcherCallbacks(); |
| |
| EXPECT_EQ(1, statistics()->GetVariable("cache_extensions")->Get()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| // Miss for the original since it is past 75% of its expiration time. The |
| // newly fetched resource is inserted into the cache. The metadata is also |
| // updated and inserted into cache. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| // The insert of the updated resource is counted as both a delete and an |
| // insert. The same goes for the metadata. |
| EXPECT_EQ(2, lru_cache()->num_deletes()); |
| |
| ClearStats(); |
| // Advance again closer to the TTL. This doesn't trigger another freshen |
| // since the last freshen updated the metadata. |
| AdvanceTimeMs(2 * Timer::kMinuteMs); |
| ValidateExpected("freshen", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "ce", kHash, "test.css", "css"))); |
| EXPECT_EQ(1, statistics()->GetVariable("cache_extensions")->Get()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| // We don't freshen again here since the last freshen updated the cache. |
| // One metadata cache hit while rewriting. |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| } |
| |
| TEST_F(RewriteContextTest, TestFreshenForEmbeddedDependency) { |
| FetcherUpdateDateHeaders(); |
| options()->ClearSignatureForTesting(); |
| options()->EnableFilter(RewriteOptions::kRewriteCss); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| // proactive_resource_freshening is off by default, so turn it on. |
| options()->set_proactive_resource_freshening(true); |
| options()->ComputeSignature(); |
| rewrite_driver()->AddFilters(); |
| |
| // Set up the resources and ttl. Ttl should be bigger than default implicit |
| // cache ttl. |
| const int kImageTtl = RewriteOptions::kDefaultImplicitCacheTtlMs * 5; |
| const int kCssTtl = RewriteOptions::kDefaultImplicitCacheTtlMs * 10; |
| const char kImageContent[] = "image1"; |
| const char kImagePath[] = "1.jpg"; |
| const char kCssPath[] = "text.css"; |
| GoogleString css_content = StrCat("{background:url(\"", |
| AbsolutifyUrl("1.jpg"), "\")}"); |
| |
| // Start with non-zero time and init the resources. |
| AdvanceTimeMs(kImageTtl / 2); |
| SetResponseWithDefaultHeaders(kImagePath, kContentTypeJpeg, kImageContent, |
| kImageTtl / Timer::kSecondMs); |
| SetResponseWithDefaultHeaders(kCssPath, kContentTypeCss, css_content, |
| kCssTtl / Timer::kSecondMs); |
| GoogleString css_url = AbsolutifyUrl("text.css"); |
| // Note: Output is absolute, because input is absolute. |
| GoogleString rewritten_url = |
| Encode(kTestDomain, "cf", "0", "text.css", "css"); |
| |
| // First fetch misses cache and resources are inserted into the cache. |
| ClearStats(); |
| ValidateExpected("first_fetch", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(4, lru_cache()->num_misses()); // cf, ic, 1.jpg, original text.css |
| EXPECT_EQ(5, lru_cache()->num_inserts()); // above + rewritten text.css |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| // text.css, 1.jpg, rewritten text.css get inserted in http cache. |
| EXPECT_EQ(3, http_cache()->cache_inserts()->Get()); |
| |
| // The ttl of the resource is the min of all its dependencies and hence |
| // kImageTtl in this case. Advance halfway and it should be a hit. |
| ClearStats(); |
| AdvanceTimeMs(kImageTtl / 2); |
| ValidateExpected("fully hit", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| |
| // Advance time close to the ttl of the image. This should cause a freshen |
| // and a fetch of the expiring image url. |
| ClearStats(); |
| AdvanceTimeMs((kImageTtl / 2) - 2 * Timer::kMinuteMs); |
| ValidateExpected("freshen", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(2, lru_cache()->num_hits()); // cf metadata, 1.jpg |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // cf metadata, 1.jpg |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); // 1.jpg expiring soon |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); // 1.jpg |
| |
| // Advance past the original TTL. There should be no cache miss and no |
| // additional fetches as the resource is already freshened. |
| ClearStats(); |
| AdvanceTimeMs(3 * Timer::kMinuteMs); |
| ValidateExpected("past original ttl", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| |
| // Advance time to Css ttl - 2 minutes. This should cause freshen of |
| // both the resources. |
| ClearStats(); |
| AdvanceTimeMs(kCssTtl - kImageTtl - 3 * Timer::kMinuteMs); |
| ValidateExpected("past highest ttl", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(5, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(5, lru_cache()->num_inserts()); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); // old rewritten css |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(3, http_cache()->cache_inserts()->Get()); |
| } |
| |
| TEST_F(RewriteContextTest, TestNoFreshenForEmbeddedDependency) { |
| FetcherUpdateDateHeaders(); |
| options()->ClearSignatureForTesting(); |
| options()->EnableFilter(RewriteOptions::kRewriteCss); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| // proactive resource freshening is off by default so no need to disable it. |
| EXPECT_FALSE(options()->proactive_resource_freshening()); |
| options()->set_proactive_resource_freshening(false); |
| options()->ComputeSignature(); |
| rewrite_driver()->AddFilters(); |
| |
| // Set up the resources and ttl. Ttl should be bigger than default implicit |
| // cache ttl. |
| const int kImageTtl = RewriteOptions::kDefaultImplicitCacheTtlMs * 5; |
| const int kCssTtl = RewriteOptions::kDefaultImplicitCacheTtlMs * 10; |
| const char kImageContent[] = "image1"; |
| const char kImagePath[] = "1.jpg"; |
| const char kCssPath[] = "text.css"; |
| GoogleString css_content = StrCat("{background:url(\"", |
| AbsolutifyUrl("1.jpg"), "\")}"); |
| |
| // Start with non-zero time and init the resources. |
| AdvanceTimeMs(kImageTtl / 2); |
| SetResponseWithDefaultHeaders(kImagePath, kContentTypeJpeg, kImageContent, |
| kImageTtl / Timer::kSecondMs); |
| SetResponseWithDefaultHeaders(kCssPath, kContentTypeCss, css_content, |
| kCssTtl / Timer::kSecondMs); |
| GoogleString css_url = AbsolutifyUrl("text.css"); |
| // Note: Output is absolute, because input is absolute. |
| GoogleString rewritten_url = |
| Encode(kTestDomain, "cf", "0", "text.css", "css"); |
| |
| // First fetch misses cache and resources are inserted into the cache. |
| ClearStats(); |
| ValidateExpected("first_fetch", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(4, lru_cache()->num_misses()); // cf, ic, 1.jpg, original text.css |
| EXPECT_EQ(5, lru_cache()->num_inserts()); // above + rewritten text.css |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| // text.css, 1.jpg, rewritten text.css get inserted in http cache. |
| EXPECT_EQ(3, http_cache()->cache_inserts()->Get()); |
| |
| // The ttl of the resource is the min of all its dependencies and hence |
| // kImageTtl in this case. Advance halfway and it should be a hit. |
| ClearStats(); |
| AdvanceTimeMs(kImageTtl / 2); |
| ValidateExpected("fully hit", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| |
| // Advance time close to the ttl of the image. This will not cause any |
| // proactive freshening since we turned it off. |
| ClearStats(); |
| AdvanceTimeMs((kImageTtl / 2) - 2 * Timer::kMinuteMs); |
| ValidateExpected("freshen", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(1, lru_cache()->num_hits()); // test.css metadata |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| |
| // Advance past the original TTL. We weren't proactively freshening |
| // the individual images that expired, but now all the resources |
| // need to be re-fetched the cache entries updated. |
| ClearStats(); |
| AdvanceTimeMs(3 * Timer::kMinuteMs); |
| ValidateExpected("past original ttl", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(4, lru_cache()->num_hits()); // test.css MD/http, 1.jpg MD/http |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(4, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| |
| // Advance time to Css ttl - 2 minutes. This will again cause no proactive |
| // freshening since we turned that off, but test.css will be expired so we |
| // will need to re-fetch it. 1.jpg will not have expired so we will not |
| // re-fetch it or check its cache entry. |
| ClearStats(); |
| AdvanceTimeMs(kCssTtl - kImageTtl - 3 * Timer::kMinuteMs); |
| ValidateExpected("past highest ttl", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(2, lru_cache()->num_hits()); // test.css MD/http |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); // old rewritten css |
| EXPECT_EQ(1, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| } |
| |
| TEST_F(RewriteContextTest, TestReuse) { |
| FetcherUpdateDateHeaders(); |
| |
| // Test to make sure we are able to avoid rewrites when inputs don't |
| // change even when they expire. |
| |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| |
| // Start with non-zero time, and init our resource.. |
| AdvanceTimeMs(kTtlMs / 2); |
| InitTrimFilters(kRewrittenResource); |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| |
| // First fetch + rewrite. |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Advance time way past when it was expired, or even when it'd live with |
| // freshening. |
| AdvanceTimeMs(kTtlMs * 10); |
| |
| // This should fetch, but can avoid calling the filter's Rewrite function. |
| ValidateExpected("forward", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| |
| // Advance some more --- make sure we fully hit from cache now (which |
| // requires the previous operation to have updated it). |
| AdvanceTimeMs(kTtlMs / 2); |
| ValidateExpected("forward2", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TestFallbackOnFetchFails) { |
| FetcherUpdateDateHeaders(); |
| InitTrimFilters(kRewrittenResource); |
| EnableDebug(); |
| |
| // Test to make sure we are able to serve stale resources if available when |
| // the fetch fails. |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| const char kDataOut[] = "data"; |
| GoogleString rewritten_url = Encode("", "tw", "0", "test.css", "css"); |
| GoogleString abs_rewritten_url = StrCat(kTestDomain, rewritten_url); |
| GoogleString response_content; |
| ResponseHeaders response_headers; |
| |
| // Serve a 500 for the CSS file. |
| ResponseHeaders bad_headers; |
| bad_headers.set_first_line(1, 1, 500, "Internal Server Error"); |
| mock_url_fetcher()->SetResponse(AbsolutifyUrl(kPath), bad_headers, ""); |
| |
| // First fetch. No rewriting happens since the fetch fails. We cache that the |
| // fetch failed for kDefaultImplicitCacheTtlMs. |
| GoogleString input_html = CssLinkHref(kPath); |
| GoogleString fetch_failure_html = |
| StrCat(input_html, "<!--Fetch failure, preventing rewriting of ", |
| kTestDomain, kPath, "-->"); |
| ValidateExpected("initial_500", input_html, fetch_failure_html); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ( |
| 0, |
| server_context()->rewrite_stats()->fallback_responses_served()->Get()); |
| |
| ClearStats(); |
| // Advance the timer by less than kDefaultImplicitCacheTtlMs. Since we |
| // remembered that the fetch failed, we don't trigger a fetch for the CSS and |
| // don't rewrite it either. |
| AdvanceTimeMs(kTtlMs / 2); |
| ValidateExpected("forward_500", input_html, fetch_failure_html); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ( |
| 0, |
| server_context()->rewrite_stats()->fallback_responses_served()->Get()); |
| |
| ClearStats(); |
| |
| // Advance the timer again so that the fetch failed is stale and update the |
| // css response to a valid 200. |
| AdvanceTimeMs(kTtlMs); |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| |
| // The resource is rewritten successfully. |
| ValidateExpected("forward_200", |
| input_html, |
| CssLinkHref(rewritten_url)); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| // Two cache inserts for the original and rewritten resource. |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| EXPECT_TRUE(FetchResourceUrl(abs_rewritten_url, &response_content, |
| &response_headers)); |
| EXPECT_STREQ(kDataOut, response_content); |
| EXPECT_EQ(HttpStatus::kOK, response_headers.status_code()); |
| EXPECT_EQ( |
| 0, |
| server_context()->rewrite_stats()->fallback_responses_served()->Get()); |
| |
| ClearStats(); |
| |
| // Advance time way past when it was expired. Set the css response to a 500 |
| // again and delete the rewritten url from cache. We don't rewrite the html. |
| // Note that we don't overwrite the stale response for the css and serve a |
| // valid 200 response to the rewrriten resource. |
| AdvanceTimeMs(kTtlMs * 10); |
| lru_cache()->Delete(HttpCacheKey(abs_rewritten_url)); |
| mock_url_fetcher()->SetResponse(AbsolutifyUrl(kPath), bad_headers, ""); |
| GoogleString expired_html = |
| StrCat(input_html, "<!--Cached content expired, preventing rewriting of ", |
| kTestDomain, kPath, "-->"); |
| |
| ValidateExpected("forward_500_fallback_served", input_html, expired_html); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ( |
| 1, |
| server_context()->rewrite_stats()->fallback_responses_served()->Get()); |
| |
| response_headers.Clear(); |
| response_content.clear(); |
| EXPECT_TRUE(FetchResourceUrl(abs_rewritten_url, &response_content, |
| &response_headers)); |
| EXPECT_STREQ(kDataOut, response_content); |
| EXPECT_EQ(HttpStatus::kOK, response_headers.status_code()); |
| |
| // Disable serving of stale resources and delete the rewritten resource from |
| // cache. We don't rewrite the html. We insert the fetch failure into cache |
| // and are unable to serve the rewritten resource. |
| options()->ClearSignatureForTesting(); |
| options()->set_serve_stale_if_fetch_error(false); |
| options()->ComputeSignature(); |
| |
| ClearStats(); |
| lru_cache()->Delete(HttpCacheKey(abs_rewritten_url)); |
| ValidateExpected("forward_500_no_fallback", input_html, fetch_failure_html); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| EXPECT_EQ( |
| 0, |
| server_context()->rewrite_stats()->fallback_responses_served()->Get()); |
| |
| response_headers.Clear(); |
| response_content.clear(); |
| EXPECT_FALSE(FetchResourceUrl(abs_rewritten_url, &response_content, |
| &response_headers)); |
| } |
| |
| TEST_F(RewriteContextTest, TestOriginalImplicitCacheTtl) { |
| options()->ClearSignatureForTesting(); |
| options()->set_metadata_cache_staleness_threshold_ms(0); |
| options()->ComputeSignature(); |
| |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| const GoogleString kOriginalRewriteUrl(Encode("", "tw", "0", |
| "test.css", "css")); |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeCss.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| // Do not call ComputeCaching before calling SetFetchResponse because it will |
| // add an explicit max-age=300 cache control header. We do not want that |
| // header in this test. |
| SetFetchResponse(AbsolutifyUrl(kPath), headers, kDataIn); |
| |
| // Start with non-zero time, and init our resource.. |
| AdvanceTimeMs(100 * Timer::kSecondMs); |
| InitTrimFilters(kRewrittenResource); |
| // First fetch + rewrite. |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Resource should be in cache. |
| ClearStats(); |
| AdvanceTimeMs(100 * Timer::kSecondMs); |
| ValidateExpected("200sec", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| // Advance time past original implicit cache ttl (300sec). |
| SetupWaitFetcher(); |
| ClearStats(); |
| AdvanceTimeMs(200 * Timer::kSecondMs); |
| // Resource is stale now. |
| ValidateNoChanges("400sec", CssLinkHref(kPath)); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TestModifiedImplicitCacheTtl) { |
| options()->ClearSignatureForTesting(); |
| options()->set_implicit_cache_ttl_ms(500 * Timer::kSecondMs); |
| options()->set_metadata_cache_staleness_threshold_ms(0); |
| options()->ComputeSignature(); |
| |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| const GoogleString kOriginalRewriteUrl(Encode("", "tw", "0", |
| "test.css", "css")); |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeCss.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| // Do not call ComputeCaching before calling SetFetchResponse because it will |
| // add an explicit max-age=300 cache control header. We do not want that |
| // header in this test. |
| SetFetchResponse(AbsolutifyUrl(kPath), headers, kDataIn); |
| |
| // Start with non-zero time, and init our resource.. |
| AdvanceTimeMs(100 * Timer::kSecondMs); |
| InitTrimFilters(kRewrittenResource); |
| // First fetch + rewrite. |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Resource should be in cache. |
| ClearStats(); |
| AdvanceTimeMs(100 * Timer::kSecondMs); |
| ValidateExpected("200sec", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| // Advance time past original implicit cache ttl (300sec). |
| ClearStats(); |
| AdvanceTimeMs(200 * Timer::kSecondMs); |
| // Resource should still be in cache. |
| ValidateExpected("400sec", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| FetcherUpdateDateHeaders(); |
| SetupWaitFetcher(); |
| ClearStats(); |
| AdvanceTimeMs(200 * Timer::kSecondMs); |
| // Resource is stale now. |
| ValidateNoChanges("600sec", CssLinkHref(kPath)); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| SetupWaitFetcher(); |
| ClearStats(); |
| AdvanceTimeMs(600 * Timer::kSecondMs); |
| // Resource is still stale. |
| ValidateNoChanges("1200sec", CssLinkHref(kPath)); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TestModifiedImplicitCacheTtlWith304) { |
| options()->ClearSignatureForTesting(); |
| options()->set_implicit_cache_ttl_ms(500 * Timer::kSecondMs); |
| options()->set_metadata_cache_staleness_threshold_ms(0); |
| options()->ComputeSignature(); |
| |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| const GoogleString kOriginalRewriteUrl(Encode("", "tw", "0", |
| "test.css", "css")); |
| ResponseHeaders headers; |
| headers.Add(HttpAttributes::kContentType, kContentTypeCss.mime_type()); |
| headers.Add(HttpAttributes::kEtag, "new"); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| // Do not call ComputeCaching before calling SetFetchResponse because it will |
| // add an explicit max-age=300 cache control header. We do not want that |
| // header in this test. |
| mock_url_fetcher()->SetConditionalResponse( |
| AbsolutifyUrl(kPath), -1, "new", headers, kDataIn); |
| |
| // Start with non-zero time, and init our resource.. |
| AdvanceTimeMs(100 * Timer::kSecondMs); |
| InitTrimFilters(kRewrittenResource); |
| // First fetch + rewrite. |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Resource should be in cache. |
| ClearStats(); |
| AdvanceTimeMs(100 * Timer::kSecondMs); |
| ValidateExpected("200sec", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| // Advance time past original implicit cache ttl (300sec). |
| ClearStats(); |
| AdvanceTimeMs(200 * Timer::kSecondMs); |
| // Resource should still be in cache. |
| ValidateExpected("400sec", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| // Modify the implicit cache ttl. |
| options()->ClearSignatureForTesting(); |
| options()->set_implicit_cache_ttl_ms(1000 * Timer::kSecondMs); |
| options()->ComputeSignature(); |
| FetcherUpdateDateHeaders(); |
| |
| SetupWaitFetcher(); |
| ClearStats(); |
| AdvanceTimeMs(200 * Timer::kSecondMs); |
| // Resource is stale now. We got a 304 this time. |
| ValidateNoChanges("600sec", CssLinkHref(kPath)); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->byte_count()); |
| |
| SetupWaitFetcher(); |
| ClearStats(); |
| AdvanceTimeMs(600 * Timer::kSecondMs); |
| // Resource is fresh this time. |
| ValidateExpected("1200sec", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TestReuseNotFastEnough) { |
| // Make sure we handle deadline passing when trying to reuse properly. |
| FetcherUpdateDateHeaders(); |
| |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| |
| // Start with non-zero time, and init our resource.. |
| AdvanceTimeMs(kTtlMs / 2); |
| InitTrimFilters(kRewrittenResource); |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| |
| // First fetch + rewrite. |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Advance time way past when it was expired, or even when it'd live with |
| // freshening. |
| AdvanceTimeMs(kTtlMs * 10); |
| |
| // Make sure we can't check for freshening fast enough... |
| SetupWaitFetcher(); |
| ValidateNoChanges("forward2.slow_fetch", CssLinkHref(kPath)); |
| |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| |
| // Next time should be fine again, though. |
| AdvanceTimeMs(kTtlMs / 2); |
| ValidateExpected("forward2", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "0", "test.css", "css"))); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, TestStaleRewriting) { |
| FetcherUpdateDateHeaders(); |
| // We use MD5 hasher instead of mock hasher so that the rewritten url changes |
| // when its content gets updated. |
| UseMd5Hasher(); |
| |
| const int kTtlMs = RewriteOptions::kDefaultImplicitCacheTtlMs; |
| const char kPath[] = "test.css"; |
| const char kDataIn[] = " data "; |
| const char kNewDataIn[] = " newdata "; |
| const GoogleString kOriginalRewriteUrl(Encode("", "tw", "jXd_OF09_s", |
| "test.css", "css")); |
| |
| options()->ClearSignatureForTesting(); |
| options()->set_metadata_cache_staleness_threshold_ms(kTtlMs / 2); |
| options()->ComputeSignature(); |
| |
| // Start with non-zero time, and init our resource.. |
| AdvanceTimeMs(kTtlMs / 2); |
| InitTrimFilters(kRewrittenResource); |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kDataIn, |
| kTtlMs / Timer::kSecondMs); |
| |
| // First fetch + rewrite. |
| ValidateExpected("initial", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(1, metadata_cache_info().num_rewrites_completed()); |
| |
| // Change the resource. |
| SetResponseWithDefaultHeaders(kPath, kContentTypeCss, kNewDataIn, |
| kTtlMs / Timer::kSecondMs); |
| |
| // Advance time past when it was expired, but within the staleness threshold. |
| AdvanceTimeMs((kTtlMs * 5)/4); |
| |
| ClearStats(); |
| // We continue to serve the stale resource. |
| SetupWaitFetcher(); |
| // We continue to rewrite the resource with the old hash. However, we noticed |
| // that the resource has changed, store it in cache and delete the old |
| // metadata. |
| ValidateExpected("stale", |
| CssLinkHref(kPath), |
| CssLinkHref(kOriginalRewriteUrl)); |
| EXPECT_EQ(0, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_hits()); |
| EXPECT_EQ(1, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(1, metadata_cache_info().num_rewrites_completed()); |
| |
| CallFetcherCallbacks(); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| // Replacing the old resource with the new resource is also considered a cache |
| // delete. The other delete is for the metadata. |
| EXPECT_EQ(2, lru_cache()->num_deletes()); |
| |
| ClearStats(); |
| // Next time, we serve the html with the new resource hash. |
| ValidateExpected("freshened", |
| CssLinkHref(kPath), |
| CssLinkHref(Encode("", "tw", "nnVv_VJ4Xn", |
| "test.css", "css"))); |
| EXPECT_EQ(1, metadata_cache_info().num_misses()); |
| EXPECT_EQ(0, metadata_cache_info().num_revalidates()); |
| EXPECT_EQ(0, metadata_cache_info().num_hits()); |
| EXPECT_EQ(0, metadata_cache_info().num_stale_rewrites()); |
| EXPECT_EQ(0, metadata_cache_info().num_successful_revalidates()); |
| EXPECT_EQ(1, metadata_cache_info().num_successful_rewrites_on_miss()); |
| EXPECT_EQ(1, metadata_cache_info().num_rewrites_completed()); |
| CallFetcherCallbacks(); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // Even though the rewrite delay is more than the deadline, the rewrite is |
| // finished by the time the response is completely flushed. |
| TEST_F(RewriteContextTest, BlockingRewrite) { |
| InitCombiningFilter(kRewriteDelayMs); |
| EnableDebug(); |
| InitResources(); |
| GoogleString combined_url = Encode("", CombiningFilter::kFilterId, |
| "0", MultiUrl("a.css", "b.css"), "css"); |
| rewrite_driver()->set_fully_rewrite_on_flush(true); |
| |
| ValidateExpected( |
| "combination_rewrite", StrCat(CssLinkHref("a.css"), CssLinkHref("b.css")), |
| CssLinkHref(combined_url)); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); // partition, and 2 inputs. |
| EXPECT_EQ(4, lru_cache()->num_inserts()); // partition, output, and 2 inputs. |
| EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // See http://code.google.com/p/modpagespeed/issues/detail?id=494. Make sure |
| // we apply domain-mapping when fetching resources, so that we get HTTP cache |
| // hits on the resource fetch based on the CSS file we optimized during the |
| // HTML rewrite. |
| TEST_F(RewriteContextTest, CssCdnMapToDifferentOrigin) { |
| int64 start_time_ms = timer()->NowMs(); |
| UseMd5Hasher(); |
| DomainLawyer* lawyer = options()->WriteableDomainLawyer(); |
| InitNestedFilter(true); |
| InitResources(); |
| lawyer->AddRewriteDomainMapping("test.com", "static.test.com", |
| message_handler()); |
| const char kCdnOriginDomain[] = "http://static.test.com/"; |
| InitResourcesToDomain(kCdnOriginDomain); |
| const char kHash[] = "WTYjEzrEWX"; |
| |
| // The newline-separated list of URLS is the format used by the simple |
| // nested rewriter used in testing. |
| // Note: These tests do not use HtmlResourceSlots and thus they do not |
| // preserve URL relativity. |
| const GoogleString kRewrittenCssContents = StrCat( |
| Encode(kTestDomain, UpperCaseRewriter::kFilterId, "lRGWyjVMXH", "a.css", |
| "css"), |
| "\n", |
| Encode(kTestDomain, UpperCaseRewriter::kFilterId, "nV7WeP5XvM", "b.css", |
| "css"), |
| "\n"); |
| |
| // First, rewrite the HTML. |
| GoogleString rewritten_css = Encode("", NestedFilter::kFilterId, |
| kHash, "c.css", "css"); |
| ValidateExpected("trimmable_async", |
| CssLinkHref("c.css"), |
| CssLinkHref(rewritten_css)); |
| |
| // Now fetch this file from its "natural" domain -- the one that we wrote |
| // into the HTML file. This works fine, and did so even with Issue 494 |
| // broken. This will hit cache and give long cache lifetimes. |
| CheckFetchFromHttpCache(StrCat(kTestDomain, rewritten_css), |
| kRewrittenCssContents, |
| start_time_ms + Timer::kYearMs); |
| |
| // Now simulate an origin-fetch from a CDN, which has been instructed |
| // to fetch from "static.test.com". This requires proper domain-mapping |
| // of Fetch urls to succeed. |
| GoogleString cdn_origin_css = Encode( |
| "http://static.test.com/", NestedFilter::kFilterId, kHash, "c.css", |
| "css"); |
| CheckFetchFromHttpCache(cdn_origin_css, kRewrittenCssContents, |
| start_time_ms + Timer::kYearMs); |
| } |
| |
| TEST_F(RewriteContextTest, CssCdnMapToDifferentOriginSharded) { |
| int64 start_time_ms = timer()->NowMs(); |
| UseMd5Hasher(); |
| DomainLawyer* lawyer = options()->WriteableDomainLawyer(); |
| InitNestedFilter(true); |
| InitResources(); |
| |
| const char kShard1[] = "http://s1.com/"; |
| const char kShard2[] = "http://s2.com/"; |
| const char kCdnOriginDomain[] = "http://static.test.com/"; |
| |
| lawyer->AddRewriteDomainMapping(kTestDomain, kCdnOriginDomain, |
| message_handler()); |
| |
| lawyer->AddShard(kTestDomain, StrCat(kShard1, ",", kShard2), |
| message_handler()); |
| InitResourcesToDomain(kCdnOriginDomain); |
| const char kHash[] = "HeWbtJb3Ks"; |
| |
| const GoogleString kRewrittenCssContents = StrCat( |
| Encode(kShard1, UpperCaseRewriter::kFilterId, "lRGWyjVMXH", "a.css", |
| "css"), "\n", |
| Encode(kShard2, UpperCaseRewriter::kFilterId, "nV7WeP5XvM", "b.css", |
| "css"), "\n"); |
| |
| // First, rewrite the HTML. |
| GoogleString rewritten_css = Encode(kShard2, NestedFilter::kFilterId, kHash, |
| "c.css", "css"); |
| ValidateExpected("trimmable_async", |
| CssLinkHref("c.css"), |
| CssLinkHref(rewritten_css)); |
| |
| // Now fetch this file from its "natural" domain -- the one that we wrote |
| // into the HTML file. This works fine, and did so even with Issue 494 |
| // broken. This will hit cache and give long cache lifetimes. |
| ClearStats(); |
| CheckFetchFromHttpCache(rewritten_css, kRewrittenCssContents, |
| start_time_ms + Timer::kYearMs); |
| |
| // Now simulate an origin-fetch from a CDN, which has been instructed |
| // to fetch from "static.test.com". This requires proper domain-mapping |
| // of Fetch urls to succeed. |
| ClearStats(); |
| GoogleString cdn_origin_css = Encode( |
| kCdnOriginDomain, NestedFilter::kFilterId, kHash, "c.css", "css"); |
| CheckFetchFromHttpCache(cdn_origin_css, kRewrittenCssContents, |
| start_time_ms + Timer::kYearMs); |
| |
| // Check from either shard -- we should always be looking up based on |
| // the rewrite domain. |
| ClearStats(); |
| GoogleString shard1_css = Encode( |
| kShard1, NestedFilter::kFilterId, kHash, "c.css", "css"); |
| CheckFetchFromHttpCache(shard1_css, kRewrittenCssContents, |
| start_time_ms + Timer::kYearMs); |
| |
| ClearStats(); |
| GoogleString shard2_css = Encode( |
| kTestDomain, NestedFilter::kFilterId, kHash, "c.css", "css"); |
| CheckFetchFromHttpCache(shard2_css, kRewrittenCssContents, |
| start_time_ms + Timer::kYearMs); |
| } |
| |
| TEST_F(RewriteContextTest, ShutdownBeforeFetch) { |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| factory()->ShutDown(); |
| GoogleString output; |
| |
| ResponseHeaders response_headers; |
| EXPECT_FALSE(FetchResourceUrl( |
| Encode(kTestDomain, "tw", "0", "b.css", "css"), |
| &output, &response_headers)); |
| EXPECT_EQ(HttpStatus::kInternalServerError, response_headers.status_code()); |
| } |
| |
| TEST_F(RewriteContextTest, InlineContextWithImplicitTtl) { |
| options()->ClearSignatureForTesting(); |
| options()->EnableFilter(RewriteOptions::kInlineCss); |
| options()->EnableFilter(RewriteOptions::kExtendCacheCss); |
| options()->set_implicit_cache_ttl_ms(100 * Timer::kSecondMs); |
| options()->set_css_inline_max_bytes(2); // so css_inline filter will bail. |
| options()->set_rewrite_deadline_ms(1); |
| |
| // Avoid noise by disabling other filters. This is so that only InlineCss |
| // and CacheExtender filters are effecting the cache hits and misses. |
| options()->SetRewriteLevel(RewriteOptions::kPassThrough); |
| options()->ComputeSignature(); |
| rewrite_driver()->AddFilters(); |
| |
| SetCacheDelayUs(2000); // so that rewrite deadline is hit. |
| ResponseHeaders headers; |
| int64 now_ms = http_cache()->timer()->NowMs(); |
| const char kContent[] = "Example"; |
| headers.Add(HttpAttributes::kContentType, kContentTypeCss.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| headers.SetDateAndCaching(now_ms, 600 * Timer::kSecondMs); |
| GoogleString css_url = AbsolutifyUrl("text.css"); |
| SetFetchResponse(css_url, headers, kContent); |
| |
| // Note: Output is absolute, because input is absolute. |
| GoogleString rewritten_url = |
| Encode(kTestDomain, "ce", "0", "text.css", "css"); |
| |
| // The first request does not get rewritten because the deadline is 1 ms |
| // and the cache delay is 2 ms. However, the rewrites happen asynchronously |
| // in the background though the HTML is served out. |
| ValidateNoChanges("ce_enabled", CssLinkHref(css_url)); |
| rewrite_driver()->WaitForCompletion(); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); // ci, ce and original text.css |
| EXPECT_EQ(3, lru_cache()->num_inserts()); // // ci, ce and original text.css |
| ClearStats(); |
| |
| // The resources are rewritten in the background and are ready for the |
| // subsequent request. |
| ValidateExpected("ce_enabled", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| rewrite_driver()->WaitForCompletion(); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| ClearStats(); |
| |
| // Advance time to past 100 s which is the implicit cache ttl. We should get |
| // the same cache hits as the resource ttl is used instead of implicit ttl. |
| // Also the rewritten resource is served from the cache. |
| AdvanceTimeMs(120 * Timer::kSecondMs); |
| ValidateExpected("ce_enabled", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| rewrite_driver()->WaitForCompletion(); |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| ClearStats(); |
| |
| // Advance time by the ttl on the resource, now we should see a different |
| // number of cache hits and misses. And the original resource is served |
| // because the rewrite deadline is smaller than the cache delay. Also the |
| // metadata is no longer valid because the resource ttl has expired. |
| AdvanceTimeMs(600 * Timer::kSecondMs); |
| GoogleString output; |
| ResponseHeaders headers1; |
| ValidateNoChanges("ce_enabled", CssLinkHref(css_url)); |
| rewrite_driver()->WaitForCompletion(); |
| // One extra lookup for text.css and its a hit. |
| EXPECT_EQ(3, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| // All the resources expired and re-inserted. ci, ce and text.css. |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| ClearStats(); |
| } |
| |
| TEST_F(RewriteContextTest, CacheTtlWithDuplicateOtherDeps) { |
| options()->ClearSignatureForTesting(); |
| options()->EnableFilter(RewriteOptions::kRewriteCss); |
| options()->EnableFilter(RewriteOptions::kConvertJpegToWebp); |
| options()->set_rewrite_deadline_ms(1); |
| options()->set_proactive_resource_freshening(true); // Enable dedup code. |
| options()->ComputeSignature(); |
| rewrite_driver()->AddFilters(); |
| |
| SetCacheDelayUs(2000); // so that rewrite deadline is hit. |
| ResponseHeaders headers; |
| int64 now_ms = http_cache()->timer()->NowMs(); |
| const char kImageContent[] = "image1"; |
| headers.Add(HttpAttributes::kContentType, kContentTypeJpeg.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| headers.SetDateAndCaching(now_ms, 200 * Timer::kSecondMs); |
| GoogleString image_url = AbsolutifyUrl("1.jpg"); |
| SetFetchResponse(image_url, headers, kImageContent); |
| |
| GoogleString css_content = StrCat("{background:url(\"", |
| AbsolutifyUrl("1.jpg"), "\")}"); |
| // Have duplicate entries to trigger the de-dup code for other dependencies. |
| GoogleString duplicate_css_content = StrCat(css_content, css_content); |
| headers.Clear(); |
| headers.Add(HttpAttributes::kContentType, kContentTypeCss.mime_type()); |
| headers.SetStatusAndReason(HttpStatus::kOK); |
| headers.SetDateAndCaching(now_ms, 600 * Timer::kSecondMs); |
| GoogleString css_url = AbsolutifyUrl("text.css"); |
| SetFetchResponse(css_url, headers, duplicate_css_content); |
| |
| // Note: Output is absolute, because input is absolute. |
| GoogleString rewritten_url = |
| Encode(kTestDomain, "cf", "0", "text.css", "css"); |
| |
| // The first request is not rewritten as there is cache miss and rewrite |
| // deadline is small. |
| ValidateNoChanges("cf_no_changes_1", CssLinkHref(css_url)); |
| rewrite_driver()->WaitForCompletion(); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(4, lru_cache()->num_misses()); // cf, ic, 1.jpg, original text.css |
| EXPECT_EQ(5, lru_cache()->num_inserts()); // above + rewritten text.css |
| ClearStats(); |
| |
| // The subsequent request should see a cache hit and no misses or inserts. |
| ValidateExpected("cf_rewritten_2", CssLinkHref(css_url), |
| CssLinkHref(rewritten_url)); |
| rewrite_driver()->WaitForCompletion(); // cf metadata cache hit |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| ClearStats(); |
| |
| // Advance time by the shorter of the ttls of the resources. We should see |
| // cache inserts as the metadata expired. And the css file is not rewritten |
| // as the rewrite deadline is too short. |
| AdvanceTimeMs(220 * Timer::kSecondMs); |
| ValidateNoChanges("cf_md_cache_miss", CssLinkHref(css_url)); |
| rewrite_driver()->WaitForCompletion(); |
| EXPECT_EQ(4, lru_cache()->num_hits()); // cf, ic, 1.jpg, original text.css |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(4, lru_cache()->num_inserts()); |
| ClearStats(); |
| } |
| |
| TEST_F(RewriteContextTest, DropFetchesAndRecover) { |
| // Construct some HTML with more resources to fetch than our rate-limiting |
| // fetcher will allow. |
| InitUpperFilter(kRewrittenResource, rewrite_driver()); |
| SetupWaitFetcher(); |
| options()->ComputeSignature(); |
| rewrite_driver()->AddFilters(); |
| |
| // Build HTML content that has more CSS links than we can fetch |
| // simultaneously. |
| const int kExcessResources = |
| TestRewriteDriverFactory::kFetchesPerHostOutgoingRequestThreshold / 3; |
| const int kResourceCount = |
| TestRewriteDriverFactory::kMaxFetchGlobalQueueSize + |
| TestRewriteDriverFactory::kFetchesPerHostOutgoingRequestThreshold + |
| kExcessResources; |
| GoogleString html; |
| for (int i = 0; i < kResourceCount; ++i) { |
| GoogleString url = StringPrintf("x%d.css", i); |
| GoogleString content = StringPrintf("a%d", i); // Rewriter will upper-case. |
| SetResponseWithDefaultHeaders(url, kContentTypeCss, content, 100 /* sec */); |
| StrAppend(&html, CssLinkHref(url)); |
| } |
| |
| // Rewrite the HTML. None of the fetches will be done before the deadline, |
| // so no changes will be made to the HTML. |
| ValidateNoChanges("no_changes_call_all_fetches_delayed", html); |
| |
| // Let's take a look at the rate-controlling fetcher's stats and make |
| // sure they are sane. |
| UpDownCounter* fetch_queue_size = statistics()->GetUpDownCounter( |
| RateController::kCurrentGlobalFetchQueueSize); |
| EXPECT_EQ(TestRewriteDriverFactory::kFetchesPerHostQueuedRequestThreshold, |
| TimedValue(RateController::kQueuedFetchCount)); |
| EXPECT_EQ(kExcessResources, TimedValue(RateController::kDroppedFetchCount)); |
| EXPECT_EQ(TestRewriteDriverFactory::kMaxFetchGlobalQueueSize, |
| fetch_queue_size->Get()); |
| |
| // Now let the fetches all go -- the ones that weren't dropped, anyway. |
| factory()->wait_url_async_fetcher()->SetPassThroughMode(true); |
| rewrite_driver()->WaitForCompletion(); |
| |
| // Having waited for those fetches to complete, there are no more queued. |
| EXPECT_EQ(0, fetch_queue_size->Get()); |
| |
| // And the rewritten page will have all but kExcessResources rewritten. |
| int num_unrewritten_css = RewriteAndCountUnrewrittenCss("1st_round", html); |
| EXPECT_EQ(kExcessResources, num_unrewritten_css); |
| |
| // OK that's not a very happy state. Even after we let the fetches |
| // finish, an immediate page refresh still won't get the entire page |
| // rewritten. But we can't grow the dropped-request list unbounded. |
| // The important thing is that we can recover in a limited amount of |
| // time, say, 10.001 seconds. |
| int64 remember_sec = server_context()->http_cache()->failure_caching_ttl_sec( |
| kFetchStatusDropped); |
| AdvanceTimeMs(remember_sec * Timer::kSecondMs + 1); |
| |
| // OK now all is well. Note that if we had a lot more fetches beyond our |
| // max queue size, we might have to wait another 10 seconds for another |
| // round of fetches to make it through. So initiate another HTML rewrite |
| // which will queue up the fetches that were previously dropped. But we |
| // delay them so they don't show up within the deadline first. The fetches |
| // will be queued up and not dropped, but we are still see kExcessResource |
| // unrewritten resources. |
| factory()->wait_url_async_fetcher()->SetPassThroughMode(false); |
| num_unrewritten_css = RewriteAndCountUnrewrittenCss("10.001_delay", html); |
| EXPECT_EQ(kExcessResources, num_unrewritten_css); |
| |
| // Release the fetches. The HTML will be fully rewritten on the next refresh. |
| factory()->wait_url_async_fetcher()->SetPassThroughMode(true); |
| rewrite_driver()->WaitForCompletion(); |
| num_unrewritten_css = RewriteAndCountUnrewrittenCss("10.001_release", html); |
| EXPECT_EQ(0, num_unrewritten_css); |
| } |
| |
| TEST_F(RewriteContextTest, DropFetchesAndRecoverFetchPath) { |
| // This is like DropFetchesAndRecover, but we test recovery on fetch, |
| // not HTML rewrite. |
| |
| // Construct some HTML with more resources to fetch than our rate-limiting |
| // fetcher will allow. |
| InitUpperFilter(kRewrittenResource, rewrite_driver()); |
| SetupWaitFetcher(); |
| options()->ComputeSignature(); |
| rewrite_driver()->AddFilters(); |
| |
| // Build HTML content that has more CSS links than we can fetch |
| // simultaneously. |
| const int kExcessResources = |
| TestRewriteDriverFactory::kFetchesPerHostOutgoingRequestThreshold / 3; |
| const int kResourceCount = |
| TestRewriteDriverFactory::kMaxFetchGlobalQueueSize + |
| TestRewriteDriverFactory::kFetchesPerHostOutgoingRequestThreshold + |
| kExcessResources; |
| GoogleString html; |
| for (int i = 0; i < kResourceCount; ++i) { |
| GoogleString url = StringPrintf("x%d.css", i); |
| GoogleString content = StringPrintf("a%d", i); // Rewriter will upper-case. |
| SetResponseWithDefaultHeaders(url, kContentTypeCss, content, 100 /* sec */); |
| StrAppend(&html, CssLinkHref(url)); |
| } |
| |
| // Rewrite the HTML. None of the fetches will be done before the deadline, |
| // so no changes will be made to the HTML. |
| ValidateNoChanges("no_changes_call_all_fetches_delayed", html); |
| |
| // Now let the fetches all go -- the ones that weren't dropped, anyway. |
| factory()->wait_url_async_fetcher()->SetPassThroughMode(true); |
| rewrite_driver()->WaitForCompletion(); |
| |
| // Try fetching rewritten versions. |
| for (int i = 0; i < kResourceCount; ++i) { |
| GoogleString base_url = StringPrintf("x%d.css", i); |
| GoogleString out_url = Encode( |
| kTestDomain, UpperCaseRewriter::kFilterId, "0", base_url, "css"); |
| EXPECT_TRUE(TryFetchResource(out_url)); |
| } |
| } |
| |
| TEST_F(RewriteContextTest, AbandonRedundantFetchInHTML) { |
| // Test that two nearly-simultaneous HTML requests which contain the same |
| // resource result in a single rewrite and fetch. We simulate this by |
| // rewriting on two RewriteDrivers with the same ServerContext. The first |
| // rewrite acquires the creation lock and then delays the rewrite. The second |
| // rewrite is not willing to delay the rewrite but abandons its rewrite |
| // attempt because it can't acquire the lock. |
| |
| // Replace the other_rewrite_driver_ with one that's derived from the |
| // same ServerContext as the primary one, as that's a better test of |
| // shared locking and multiple rewrites on the same task. |
| RewriteOptions* new_options_ = other_options_->Clone(); |
| delete other_rewrite_driver_; |
| other_rewrite_driver_ = MakeDriver(server_context_, new_options_); |
| InitResources(); |
| |
| // We use fake filters since they provide delayed rewriter functionality. |
| FakeFilter* fake1 = |
| new FakeFilter(TrimWhitespaceRewriter::kFilterId, rewrite_driver_, |
| semantic_type::kStylesheet); |
| fake1->set_exceed_deadline(true); |
| rewrite_driver_->AppendRewriteFilter(fake1); |
| rewrite_driver_->AddFilters(); |
| FakeFilter* fake2 = |
| new FakeFilter(TrimWhitespaceRewriter::kFilterId, other_rewrite_driver_, |
| semantic_type::kStylesheet); |
| fake2->set_exceed_deadline(false); // This is default, but being explicit. |
| other_rewrite_driver_->AppendRewriteFilter(fake2); |
| other_rewrite_driver_->AddFilters(); |
| |
| // Optimize the page once |
| const GoogleString encoded = |
| Encode("", TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| ValidateNoChanges("trimmable", CssLinkHref("a.css")); |
| |
| // Optimize the same page again but with a driver that doesn't have a delayed |
| // rewriter. |
| SetActiveServer(kSecondary); |
| ValidateNoChanges("trimmable2", CssLinkHref("a.css")); |
| |
| SetActiveServer(kPrimary); |
| EXPECT_EQ(0, fake1->num_rewrites()); |
| EXPECT_EQ(0, fake2->num_rewrites()); |
| // Note: The lru_cache is shared between the two drivers. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); // meta twice and http once |
| EXPECT_EQ(1, lru_cache()->num_inserts()); // original resource |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Advance the time and make sure the rewrite does eventually complete. |
| ClearStats(); |
| AdvanceTimeMs(1); // The fake filter waits until just after the deadline. |
| EXPECT_EQ(1, fake1->num_rewrites()); |
| EXPECT_EQ(0, fake2->num_rewrites()); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // metadata and optimized |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| // Make sure that we can fetch it. |
| ClearStats(); |
| ValidateExpected("trimmable3", CssLinkHref("a.css"), CssLinkHref(encoded)); |
| EXPECT_EQ(1, fake1->num_rewrites()); // ClearStats didn't clear this. |
| EXPECT_EQ(0, fake2->num_rewrites()); |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(RewriteContextTest, WaitForRedundantRewriteInFetchAfterHtml) { |
| // Test that an HTML request with a resource followed by a reconstruction |
| // request for the same resource only rewrites and fetches once. We simulate |
| // this by rewriting on two RewriteDrivers with the same ServerContext. The |
| // first rewrite acquires the creation lock and then delays the rewrite. The |
| // second waits for the first to complete. |
| |
| // Replace the other_rewrite_driver_ with one that's derived from the |
| // same ServerContext as the primary one, as that's a better test of |
| // shared locking and multiple rewrites on the same task. |
| RewriteOptions* new_options_ = other_options_->Clone(); |
| delete other_rewrite_driver_; |
| other_rewrite_driver_ = MakeDriver(server_context_, new_options_); |
| InitResources(); |
| |
| // We use fake filters since they provide delayed rewriter functionality. |
| FakeFilter* fake1 = |
| new FakeFilter(TrimWhitespaceRewriter::kFilterId, rewrite_driver_, |
| semantic_type::kStylesheet); |
| fake1->set_exceed_deadline(true); |
| rewrite_driver_->AppendRewriteFilter(fake1); |
| rewrite_driver_->AddFilters(); |
| FakeFilter* fake2 = |
| new FakeFilter(TrimWhitespaceRewriter::kFilterId, other_rewrite_driver_, |
| semantic_type::kStylesheet); |
| fake2->set_exceed_deadline(false); // This is default, but being explicit. |
| other_rewrite_driver_->AppendRewriteFilter(fake2); |
| other_rewrite_driver_->AddFilters(); |
| |
| // Optimize the page once |
| ValidateNoChanges("trimmable", CssLinkHref("a.css")); |
| EXPECT_EQ(0, fake1->num_rewrites()); |
| EXPECT_EQ(0, fake2->num_rewrites()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // Optimize the same resource as a .pagespeed. resource on a driver that |
| // doesn't have a delayed rewriter. It should wait for the first rewrite to |
| // finish and return the optimized result, but we should have only fetched and |
| // optimized once. |
| ClearStats(); |
| SetActiveServer(kSecondary); |
| GoogleString content; |
| EXPECT_TRUE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a.css", "css", &content)); |
| EXPECT_EQ(StrCat(" a :", TrimWhitespaceRewriter::kFilterId), content); |
| |
| SetActiveServer(kPrimary); |
| // The initial http cache lookup will fail. Then the lock attempt will block. |
| // By the time the lock is released the metadata and retry at the http cache |
| // will succeed. |
| // Note: The lru_cache is shared between the two drivers. |
| EXPECT_EQ(2, lru_cache()->num_hits()); // meta and http of the fetch |
| EXPECT_EQ(1, lru_cache()->num_misses()); // http of the html request |
| EXPECT_EQ(2, lru_cache()->num_inserts()); // meta and http of the fetch |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, fake1->num_rewrites()); |
| EXPECT_EQ(0, fake2->num_rewrites()); |
| } |
| |
| TEST_F(RewriteContextTest, WaitForRedundantFetchInFetchAfterFetch) { |
| // Test that a .pagespeed. fetch for a resource followed by another |
| // .pagespeed. fetch for the same resource only rewrites and fetches from |
| // origin once. We simulate this by rewriting on two RewriteDrivers with the |
| // same ServerContext. The first fetch acquires the creation lock and then |
| // delays the rewrite. The second fetch1 waits for the first to complete. |
| |
| // Replace the other_rewrite_driver_ with one that's derived from the |
| // same ServerContext as the primary one, as that's a better test of |
| // shared locking and multiple rewrites on the same task. |
| RewriteOptions* new_options_ = other_options_->Clone(); |
| delete other_rewrite_driver_; |
| other_rewrite_driver_ = MakeDriver(server_context_, new_options_); |
| InitResources(); |
| |
| // We use fake filters since they provide delayed rewriter functionality. |
| FakeFilter* fake1 = |
| new FakeFilter(TrimWhitespaceRewriter::kFilterId, rewrite_driver_, |
| semantic_type::kStylesheet); |
| fake1->set_exceed_deadline(true); |
| rewrite_driver_->AppendRewriteFilter(fake1); |
| rewrite_driver_->AddFilters(); |
| FakeFilter* fake2 = |
| new FakeFilter(TrimWhitespaceRewriter::kFilterId, other_rewrite_driver_, |
| semantic_type::kStylesheet); |
| fake2->set_exceed_deadline(false); // This is default, but being explicit. |
| other_rewrite_driver_->AppendRewriteFilter(fake2); |
| other_rewrite_driver_->AddFilters(); |
| |
| // Do a .pagespeed. fetch but delay the rewrite until past the deadline. |
| GoogleString content1; |
| StringAsyncFetch async_fetch(rewrite_driver_->request_context(), &content1); |
| GoogleString url = Encode(kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", |
| "a.css", "css"); |
| // Note that this is rewrite_driver_->FetchResource and not |
| // RewriteTestBase::FetchResource, therefore WaitForShutdown will not be |
| // called. |
| EXPECT_TRUE(rewrite_driver_->FetchResource(url, &async_fetch)); |
| |
| // Verify that the rewrite is still pending, so the lock should still be held. |
| EXPECT_EQ(0, fake1->num_rewrites()); |
| |
| // Fetch again on a driver that doesn't have a delayed rewriter. It should |
| // wait for the first rewrite to finish and return the optimized result, but |
| // we should have only fetched and optimized once. |
| SetActiveServer(kSecondary); |
| GoogleString content2; |
| EXPECT_TRUE(FetchResource(kTestDomain, TrimWhitespaceRewriter::kFilterId, |
| "a.css", "css", &content2)); |
| EXPECT_EQ(StrCat(" a :", TrimWhitespaceRewriter::kFilterId), content2); |
| |
| SetActiveServer(kPrimary); |
| // Let the first driver wrap up. |
| rewrite_driver_->WaitForShutDown(); |
| |
| // We have the stats for both rewrites here: |
| // Fetch 1: http, metadata, and original resource misses, one fetch, and three |
| // inserts. |
| // Fetch 2: http miss, lock, metadata hit, http hit and return. |
| EXPECT_EQ(2, lru_cache()->num_hits()); |
| EXPECT_EQ(4, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, fake1->num_rewrites()); |
| EXPECT_EQ(0, fake2->num_rewrites()); |
| } |
| |
| class FailOnHashMismatchFilter : public RewriteFilter { |
| public: |
| static const char kFilterId[]; |
| |
| explicit FailOnHashMismatchFilter(RewriteDriver* driver) |
| : RewriteFilter(driver) {} |
| |
| class Context : public SingleRewriteContext { |
| public: |
| explicit Context(RewriteDriver* driver) |
| : SingleRewriteContext(driver, NULL, NULL) {} |
| |
| virtual bool FailOnHashMismatch() const { return true; } |
| |
| virtual const char* id() const { return kFilterId; } |
| virtual OutputResourceKind kind() const { return kRewrittenResource; } |
| virtual void RewriteSingle( |
| const ResourcePtr& input, const OutputResourcePtr& output) { |
| // Do nothing. |
| Driver()->Write(ResourceVector(1, input), "output", &kContentTypeCss, |
| kUtf8Charset, output.get()); |
| RewriteDone(kRewriteOk, 0); |
| } |
| }; |
| |
| virtual RewriteContext* MakeRewriteContext() { |
| return new Context(driver()); |
| } |
| |
| virtual const char* id() const { return kFilterId; } |
| virtual const char* Name() const { return "FailOnHashMismatchFilter"; } |
| virtual void StartDocumentImpl() {} |
| virtual void StartElementImpl(HtmlElement* element) {} |
| virtual void EndElementImpl(HtmlElement* element) {} |
| }; |
| |
| const char FailOnHashMismatchFilter::kFilterId[] = |
| "fail_on_hash_mismatch_filter"; |
| |
| TEST_F(RewriteContextTest, FailOnHashMismatch) { |
| SetResponseWithDefaultHeaders("foo.css", kContentTypeCss, "input", 100); |
| |
| rewrite_driver()->AppendRewriteFilter( |
| new FailOnHashMismatchFilter(rewrite_driver())); |
| rewrite_driver()->AddFilters(); |
| |
| GoogleString bogus_resource = |
| Encode(kTestDomain, FailOnHashMismatchFilter::kFilterId, "Bogus-hash", |
| "foo.css", "css"); |
| |
| GoogleString correct_hash = "0"; |
| GoogleString correct_resource = |
| Encode(kTestDomain, FailOnHashMismatchFilter::kFilterId, correct_hash, |
| "foo.css", "css"); |
| |
| // Run several times to cover different levels of caching. |
| for (int i = 0; i < 3; ++i) { |
| // Fail on hash mismatch. |
| GoogleString contents; |
| ResponseHeaders headers; |
| EXPECT_TRUE(FetchResourceUrl(bogus_resource, &contents, &headers)); |
| EXPECT_EQ(HttpStatus::kNotFound, headers.status_code()); |
| EXPECT_STREQ(RewriteContext::kHashMismatchMessage, contents); |
| |
| // Don't fail on correct hash. |
| EXPECT_TRUE(FetchResourceUrl(correct_resource, &contents)); |
| EXPECT_STREQ("output", contents); |
| } |
| |
| // Correctly re-construct result when HTTP resource falls out of cache, but |
| // metadata is still in cache. |
| http_cache()->Delete(correct_resource, ""); |
| GoogleString contents; |
| EXPECT_TRUE(FetchResourceUrl(correct_resource, &contents)); |
| EXPECT_STREQ("output", contents); |
| } |
| |
| } // namespace net_instaweb |