| /* |
| * Copyright 2013 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: jkarlin@google.com (Josh Karlin) |
| |
| // Unit-test the distributed pathways through the RewriteContext class. |
| // In these tests the RewriteTestBase::other_* objects represent the task |
| // that gets distributed to. |
| |
| #include "net/instaweb/rewriter/public/rewrite_context.h" |
| |
| #include "net/instaweb/http/public/async_fetch.h" |
| #include "net/instaweb/http/public/counting_url_async_fetcher.h" |
| #include "net/instaweb/http/public/http_cache.h" |
| #include "net/instaweb/http/public/rate_controlling_url_async_fetcher.h" |
| #include "net/instaweb/rewriter/cached_result.pb.h" |
| #include "net/instaweb/rewriter/public/fake_filter.h" |
| #include "net/instaweb/rewriter/public/output_resource_kind.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_options.h" |
| #include "net/instaweb/rewriter/public/rewrite_stats.h" |
| #include "net/instaweb/rewriter/public/server_context.h" |
| #include "net/instaweb/rewriter/public/test_distributed_fetcher.h" |
| #include "net/instaweb/rewriter/public/test_rewrite_driver_factory.h" |
| #include "pagespeed/kernel/base/base64_util.h" |
| #include "pagespeed/kernel/base/basictypes.h" |
| #include "pagespeed/kernel/base/gtest.h" |
| #include "pagespeed/kernel/base/scoped_ptr.h" |
| #include "pagespeed/kernel/base/statistics.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/base/string_util.h" |
| #include "pagespeed/kernel/base/timer.h" // for Timer, etc |
| #include "pagespeed/kernel/cache/lru_cache.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/thread/mock_scheduler.h" |
| |
| namespace net_instaweb { |
| |
| class HtmlFilter; |
| |
| namespace { |
| |
| // Hook provided to TestRewriteDriverFactory to add a new filter when a |
| // rewrite_driver is created. A CreateFilterCallback must outlive any filters it |
| // creates as the filter's id comes from this. |
| class CreateFilterCallback |
| : public TestRewriteDriverFactory::CreateFilterCallback { |
| public: |
| CreateFilterCallback(const StringPiece& id, bool blocking) |
| : id_(id.as_string()), blocking_(blocking) {} |
| virtual ~CreateFilterCallback() {} |
| |
| virtual HtmlFilter* Done(RewriteDriver* driver) { |
| FakeFilter* filter = new FakeFilter(id_.c_str(), driver, |
| semantic_type::kStylesheet); |
| if (blocking_) { |
| filter->set_exceed_deadline(true); |
| } |
| return filter; |
| } |
| |
| private: |
| GoogleString id_; |
| bool blocking_; |
| DISALLOW_COPY_AND_ASSIGN(CreateFilterCallback); |
| }; |
| |
| } // namespace |
| |
| // A class for testing the distributed paths through the rewrite context. It |
| // uses the RewriteContextTestBase's "other" RewriteDriver, factory, and options |
| // as a second task to perform distributed rewrites on. Call |
| // SetupDistributedTest to configure the test class. |
| class DistributedRewriteContextTest : public RewriteContextTestBase { |
| protected: |
| enum HttpRequestType { |
| kHeadRequest, |
| kGetRequest |
| }; |
| |
| DistributedRewriteContextTest() { |
| distributed_rewrite_failures_ = statistics()->GetVariable( |
| RewriteContext::kNumDistributedRewriteFailures); |
| distributed_rewrite_successes_ = statistics()->GetVariable( |
| RewriteContext::kNumDistributedRewriteSuccesses); |
| distributed_metadata_failures_ = statistics()->GetVariable( |
| RewriteContext::kNumDistributedMetadataFailures); |
| fetch_failures_ = |
| statistics()->GetVariable(RewriteStats::kNumResourceFetchFailures); |
| fetch_successes_ = |
| statistics()->GetVariable(RewriteStats::kNumResourceFetchSuccesses); |
| } |
| |
| // Sets the options to be the same for the two tasks and configures a shared |
| // LRU cache between them. Note that when a distributed call is made, the |
| // fetcher will call the RewriteContextTestBase's "other" driver directly (see |
| // TestDistributedFetcher). |
| void SetupDistributedTest() { |
| SetupSharedCache(); |
| options_->DistributeFilter(TrimWhitespaceRewriter::kFilterId); |
| options_->set_distributed_rewrite_servers("example.com:80"); |
| options_->set_distributed_rewrite_key("1234123"); |
| // Make sure they have the same options so that they generate the same |
| // metadata keys. |
| other_options()->Merge(*options()); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| // Default to empty request headers. |
| rewrite_driver()->SetRequestHeaders(request_headers_); |
| } |
| |
| void InitTwoFilters(OutputResourceKind kind) { |
| InitUpperFilter(kind, rewrite_driver()); |
| InitUpperFilter(kind, other_rewrite_driver()); |
| options_->DistributeFilter(UpperCaseRewriter::kFilterId); |
| SetupDistributedTest(); |
| } |
| |
| // A wrapper function with friend access to RewriteDriver. |
| bool DistributeFetch(const StringPiece& url, const StringPiece& filter_id, |
| AsyncFetch* async_fetch) { |
| return rewrite_driver_->DistributeFetch(url, filter_id, async_fetch); |
| } |
| |
| // TODO(jkarlin): Instead of CheckDistributedFetch where we pass in the |
| // expected values an alternative idea is to create a DistributedFetch |
| // function that returns a struct with all of the resulting stats that we can |
| // EXPECT_EQ from the calling sites. That might be more readable. |
| |
| void CheckDistributedFetch(int distributed_fetch_success_count, |
| int distributed_fetch_failure_count, |
| int local_fetch_required, int rewritten) { |
| EXPECT_EQ(distributed_fetch_success_count + distributed_fetch_failure_count, |
| counting_distributed_fetcher()->fetch_count()); |
| EXPECT_EQ(local_fetch_required, |
| counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ( |
| 0, other_factory_->counting_distributed_async_fetcher()->fetch_count()); |
| EXPECT_EQ(distributed_fetch_success_count, |
| distributed_rewrite_successes_->Get()); |
| EXPECT_EQ(distributed_fetch_failure_count, |
| distributed_rewrite_failures_->Get()); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(rewritten, other_trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, distributed_metadata_failures_->Get()); |
| } |
| |
| bool FetchValidatedMetadata(StringPiece key, StringPiece input_url, |
| StringPiece correct_url, |
| HttpRequestType request_type) { |
| GoogleString output; |
| ResponseHeaders response_headers; |
| RequestHeaders req_headers; |
| bool valid_metadata = false; |
| req_headers.Add(HttpAttributes::kXPsaRequestMetadata, key); |
| if (request_type == kHeadRequest) { |
| req_headers.set_method(RequestHeaders::kHead); |
| } |
| rewrite_driver()->SetRequestHeaders(req_headers); |
| EXPECT_TRUE( |
| FetchResourceUrl(input_url, &req_headers, &output, &response_headers)); |
| |
| // Check if the metadata is valid. |
| if (response_headers.Has(HttpAttributes::kXPsaResponseMetadata)) { |
| GoogleString encoded_serialized = |
| response_headers.Lookup1(HttpAttributes::kXPsaResponseMetadata); |
| GoogleString decoded_serialized; |
| Mime64Decode(encoded_serialized, &decoded_serialized); |
| OutputPartitions partitions; |
| EXPECT_TRUE(partitions.ParseFromString(decoded_serialized)); |
| EXPECT_STREQ(correct_url, partitions.partition(0).url()); |
| valid_metadata = true; |
| } |
| |
| // If we did a HEAD request we don't expect any output |
| if (request_type == kHeadRequest) { |
| EXPECT_STREQ("", output); |
| } |
| return valid_metadata; |
| } |
| |
| RequestHeaders request_headers_; // default request headers |
| Variable* fetch_failures_; |
| Variable* fetch_successes_; |
| Variable* distributed_rewrite_failures_; |
| Variable* distributed_rewrite_successes_; |
| Variable* distributed_metadata_failures_; |
| }; |
| |
| TEST_F(DistributedRewriteContextTest, |
| NoDistributedHtmlRewriteWithoutSettingKeyOnDistributedTask) { |
| SetupDistributedTest(); |
| // Clear the distributed task's key. The end result should be unoptimized |
| // due to no metadata being returned since the key couldn't be validated. |
| other_options()->ClearSignatureForTesting(); |
| other_options()->set_distributed_rewrite_key(""); |
| other_server_context()->ComputeSignature(other_options()); |
| ValidateNoChanges("trimmable", CssLinkHref("a.css")); |
| EXPECT_EQ(1, counting_distributed_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(DistributedRewriteContextTest, |
| NoDistributedHtmlRewriteWithDifferentKeyOnDistributedTask) { |
| SetupDistributedTest(); |
| // Set a different distributed task key from the ingress task. The end result |
| // should be unoptimized due to no metadata being returned since the key |
| // couldn't be validated. |
| other_options()->ClearSignatureForTesting(); |
| other_options()->set_distributed_rewrite_key("wrong key"); |
| other_server_context()->ComputeSignature(other_options()); |
| ValidateNoChanges("trimmable", CssLinkHref("a.css")); |
| EXPECT_EQ(1, counting_distributed_fetcher()->fetch_count()); |
| // But the optimization should have happened on the distributed task and |
| // should be cached. |
| EXPECT_EQ( |
| 3, lru_cache()->num_inserts()); // metadata, resource, optimized resource |
| EXPECT_EQ(1, other_trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| } |
| |
| // Copy of the RewriteContextTest.TrimRewrittenOptimizable test modified for |
| // distributed rewrites. |
| TEST_F(DistributedRewriteContextTest, TrimRewrittenOptimizable) { |
| SetupDistributedTest(); |
| |
| // Ingress task: Misses on metadata and distributes. |
| // Rewrite task: Misses on metadata, misses on http data, writes original |
| // resources, optimized resource, and metadata. |
| ValidateExpected( |
| "trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", TrimWhitespaceRewriter::kFilterId, "0", |
| "a.css", "css"))); |
| |
| CheckDistributedFetch(1, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 1); // number of rewrites |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| 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", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", TrimWhitespaceRewriter::kFilterId, "0", |
| "a.css", "css"))); |
| CheckDistributedFetch(0, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| } |
| |
| // Copy of the RewriteContextTest.TrimRewrittenNonOptimizable test modified for |
| // distributed rewrites. |
| TEST_F(DistributedRewriteContextTest, TrimRewrittenNonOptimizable) { |
| SetupDistributedTest(); |
| |
| // 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")); |
| CheckDistributedFetch(1, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 1); // number of rewrites |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| ClearStats(); |
| |
| // We should have cached the failed rewrite, no misses, fetches, or inserts. |
| ValidateNoChanges("no_trimmable", CssLinkHref("b.css")); |
| CheckDistributedFetch(0, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| } |
| |
| // Copy of the RewriteContextTest.TrimRepeatedOptimizable test modified for |
| // distributed rewrites. |
| TEST_F(DistributedRewriteContextTest, TrimRepeatedOptimizable) { |
| // Make sure two instances of the same link are handled properly, |
| // when optimization succeeds. |
| SetupDistributedTest(); |
| ValidateExpected( |
| "trimmable2", StrCat(CssLinkHref("a.css"), CssLinkHref("a.css")), |
| StrCat(CssLinkHref(Encode("", TrimWhitespaceRewriter::kFilterId, |
| "0", "a.css", "css")), |
| CssLinkHref(Encode("", TrimWhitespaceRewriter::kFilterId, |
| "0", "a.css", "css")))); |
| CheckDistributedFetch(1, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 1); // number of rewrites |
| } |
| |
| TEST_F(DistributedRewriteContextTest, TwoFilters) { |
| InitTwoFilters(kOnTheFlyResource); |
| |
| ValidateExpected( |
| "two_filters", CssLinkHref("a.css"), |
| CssLinkHref( |
| Encode("", TrimWhitespaceRewriter::kFilterId, "0", |
| Encode("", UpperCaseRewriter::kFilterId, "0", "a.css", "css"), |
| "css"))); |
| EXPECT_EQ(1, distributed_rewrite_successes_->Get()); |
| EXPECT_EQ(0, distributed_rewrite_failures_->Get()); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); // not distributed |
| EXPECT_EQ(0, other_trim_filter_->num_rewrites()); |
| } |
| |
| // Same as TwoFilters but this time write to HTTP cache. |
| TEST_F(DistributedRewriteContextTest, TwoFiltersRewritten) { |
| InitTwoFilters(kRewrittenResource); |
| |
| ValidateExpected( |
| "two_filters", CssLinkHref("a.css"), |
| CssLinkHref( |
| Encode("", TrimWhitespaceRewriter::kFilterId, "0", |
| Encode("", UpperCaseRewriter::kFilterId, "0", "a.css", "css"), |
| "css"))); |
| EXPECT_EQ(1, distributed_rewrite_successes_->Get()); |
| EXPECT_EQ(0, distributed_rewrite_failures_->Get()); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); // not distributed |
| EXPECT_EQ(0, other_trim_filter_->num_rewrites()); |
| // num_hits = 0 proves that we didn't use the cache to pipe the output of the |
| // first filter in the chain to the second, instead we used the slot like we |
| // were supposed to. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| // Miss uc (UpperCaseRewriter) metadata on ingress and distributed task. |
| // Miss http input on distributed task. |
| // Miss tw metadata on ingress task (but don't distribute). |
| EXPECT_EQ(4, lru_cache()->num_misses()); |
| // uc (UpperCaseRewriter) inserts metadata, original http content, and |
| // optimized http content. |
| // tw filter inserts metadata and optimized http content. |
| EXPECT_EQ(5, lru_cache()->num_inserts()); |
| } |
| |
| TEST_F(DistributedRewriteContextTest, TwoFiltersDelayedFetches) { |
| other_factory_->SetupWaitFetcher(); |
| InitTwoFilters(kOnTheFlyResource); |
| test_distributed_fetcher_.set_blocking_fetch(false); |
| |
| ValidateNoChanges("trimmable1", CssLinkHref("a.css")); |
| OtherCallFetcherCallbacks(); |
| rewrite_driver_->WaitForShutDown(); |
| ValidateExpected( |
| "delayed_fetches", CssLinkHref("a.css"), |
| CssLinkHref( |
| Encode("", TrimWhitespaceRewriter::kFilterId, "0", |
| Encode("", UpperCaseRewriter::kFilterId, "0", "a.css", "css"), |
| "css"))); |
| EXPECT_EQ(1, distributed_rewrite_successes_->Get()); |
| EXPECT_EQ(0, distributed_rewrite_failures_->Get()); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); // not distributed |
| EXPECT_EQ(0, other_trim_filter_->num_rewrites()); |
| } |
| |
| TEST_F(DistributedRewriteContextTest, RepeatedTwoFilters) { |
| // Make sure if we have repeated URLs and chaining, it still works right. Note |
| // that both trim and upper are distributed, but when chained only the first |
| // should distribute. |
| InitTwoFilters(kRewrittenResource); |
| |
| ValidateExpected( |
| "two_filters2", StrCat(CssLinkHref("a.css"), CssLinkHref("a.css")), |
| StrCat(CssLinkHref(Encode( |
| "", TrimWhitespaceRewriter::kFilterId, "0", |
| Encode("", UpperCaseRewriter::kFilterId, "0", "a.css", "css"), |
| "css")), |
| CssLinkHref(Encode( |
| "", TrimWhitespaceRewriter::kFilterId, "0", |
| Encode("", UpperCaseRewriter::kFilterId, "0", "a.css", "css"), |
| "css")))); |
| EXPECT_EQ(1, distributed_rewrite_successes_->Get()); |
| EXPECT_EQ(0, distributed_rewrite_failures_->Get()); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); // not distributed |
| EXPECT_EQ(0, other_trim_filter_->num_rewrites()); |
| } |
| |
| // Test that we can successfully reconstruct a resource built from chained |
| // filters when rewrites are distributed. |
| TEST_F(DistributedRewriteContextTest, ReconstructDistributedTwoFilter) { |
| // Need to use normal filters that the ServerContext::decoding_driver knows |
| // about so that we can IsPagespeedResource() can recognize the filters in |
| // the chained URL. |
| options_->EnableFilter(RewriteOptions::kCombineCss); |
| options_->EnableFilter(RewriteOptions::kRewriteCss); |
| options_->DistributeFilter(RewriteOptions::kCssFilterId); |
| SetupDistributedTest(); |
| |
| SetResponseWithDefaultHeaders("a.css", kContentTypeCss, |
| " div { display: block; }", 100); |
| |
| GoogleString encoded_url = Encode( |
| kTestDomain, RewriteOptions::kCssCombinerId, "1", |
| Encode("", RewriteOptions::kCssFilterId, "0", "a.css", "css"), "css"); |
| |
| // Fetch the .pagespeed. resource and ensure that the two-filter |
| // reconstruction is distributed properly. |
| GoogleString content; |
| ResponseHeaders response_headers; |
| RequestHeaders request_headers; |
| EXPECT_TRUE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| // Content should be optimized (note that the combiner doesn't do anything in |
| // this case, but it at least ran). |
| EXPECT_EQ("div{display:block}", content); |
| EXPECT_EQ(1, distributed_rewrite_successes_->Get()); |
| EXPECT_EQ(0, distributed_rewrite_failures_->Get()); |
| |
| // Ingress task starts on cc filter, misses HTTP, metadata once, and then |
| // fetches its CssFilter input, which distributes. |
| // Distributed task CssFilter misses HTTP, metadata once, input resource once, |
| // and then fetches the input resource and inserts the metadata, original |
| // resource, and optimized resource into cache. |
| // Ingress task writes its metadata and optimized to cache. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(5, lru_cache()->num_misses()); |
| EXPECT_EQ(5, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(3, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(3, http_cache()->cache_inserts()->Get()); |
| } |
| |
| // Test that we can successfully reconstruct a resource built from chained |
| // filters when rewrites are distributed and make sure that it blocks. |
| TEST_F(DistributedRewriteContextTest, ReconstructDistributedTwoFilterBlocks) { |
| // We need to use filters with ids that the decoding driver knows about so |
| // that IsPagespeedResource can recognize the filters in the encoded URL. So |
| // we use a fake CSS filter and combiner. We give the ingress task one of each |
| // and a fake filter to the distributed task, which times out. |
| FakeFilter* fake_css_filter = |
| new FakeFilter(RewriteOptions::kCssFilterId, rewrite_driver(), |
| semantic_type::kStylesheet); |
| fake_css_filter->set_exceed_deadline(true); |
| fake_css_filter->set_output_content_type(&kContentTypeCss); |
| FakeFilter* fake_css_combiner = |
| new FakeFilter(RewriteOptions::kCssCombinerId, rewrite_driver(), |
| semantic_type::kStylesheet); |
| fake_css_combiner->set_output_content_type(&kContentTypeCss); |
| FakeFilter* other_fake_css_filter = |
| new FakeFilter(RewriteOptions::kCssFilterId, other_rewrite_driver(), |
| semantic_type::kStylesheet); |
| other_fake_css_filter->set_exceed_deadline(true); |
| other_fake_css_filter->set_output_content_type(&kContentTypeCss); |
| rewrite_driver()->AppendRewriteFilter(fake_css_filter); |
| rewrite_driver()->AppendRewriteFilter(fake_css_combiner); |
| other_rewrite_driver()->AppendRewriteFilter(other_fake_css_filter); |
| |
| // Distribute the CSS filter. |
| options_->DistributeFilter(RewriteOptions::kCssFilterId); |
| |
| // Make sure that any cloned drivers get fake CSS filters too. |
| CreateFilterCallback create_filter_callback(RewriteOptions::kCssFilterId, |
| true); |
| CreateFilterCallback other_create_filter_callback( |
| RewriteOptions::kCssFilterId, true); |
| factory()->AddCreateFilterCallback(&create_filter_callback); |
| other_factory()->AddCreateFilterCallback(&other_create_filter_callback); |
| |
| SetupDistributedTest(); |
| |
| // This is the encoded URL to reconstruct. |
| GoogleString encoded_url = Encode( |
| kTestDomain, RewriteOptions::kCssCombinerId, "1", |
| Encode("", RewriteOptions::kCssFilterId, "0", "a.css", "css"), "css"); |
| |
| // Reconstruct the chained rewrite. |
| GoogleString content; |
| ResponseHeaders response_headers; |
| RequestHeaders request_headers; |
| EXPECT_TRUE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| EXPECT_EQ(StrCat(" a :", RewriteOptions::kCssFilterId, ":", |
| RewriteOptions::kCssCombinerId), |
| content); |
| EXPECT_EQ(1, distributed_rewrite_successes_->Get()); |
| EXPECT_EQ(0, distributed_rewrite_failures_->Get()); |
| |
| // Ingress task starts on cc filter, misses HTTP, metadata, and then fetches |
| // its CssFilter input, which distributes. |
| // Distributed task CssFilter misses HTTP, metadata, and input resource and |
| // then fetches the input resource and inserts the metadata, original |
| // resource, and optimized resource into cache. |
| // Ingress task writes its metadata and optimized resource to cache. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(5, lru_cache()->num_misses()); |
| EXPECT_EQ(5, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(3, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(3, http_cache()->cache_inserts()->Get()); |
| } |
| |
| // Simulate distributed fetch failure and ensure that we don't modify the URL. |
| TEST_F(DistributedRewriteContextTest, IngressDistributedRewriteFailFallback) { |
| SetupDistributedTest(); |
| // Break the response after the headers have written but before data is |
| // complete. |
| test_distributed_fetcher()->set_fail_after_headers(true); |
| ValidateNoChanges("trimmable", CssLinkHref("a.css")); |
| |
| // Ingress: Misses metadata, and does not optimize after unsuccessful |
| // distributed fetch. |
| // Distributed task: Misses metadata and original resource. Inserts metadata, |
| // original, and optimized. Returned stream is broken. |
| CheckDistributedFetch(0, // successful distributed fetches |
| 1, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 1); // number of rewrites |
| EXPECT_EQ(0, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| |
| // Try again, this time we should have the result in shared cache. |
| ValidateExpected( |
| "trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", TrimWhitespaceRewriter::kFilterId, "0", |
| "a.css", "css"))); |
| |
| // Ingress: Misses metadata, and does not optimize after unsuccessful |
| // distributed fetch. |
| // Distributed task: Misses metadata and original resource. Inserts metadata, |
| // original, and optimized. Returned stream is broken. |
| CheckDistributedFetch(0, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| } |
| |
| // If the distributed fetcher returns a 404 then that's what needs to be |
| // returned. |
| TEST_F(DistributedRewriteContextTest, DistributedRewriteNotFound) { |
| SetupDistributedTest(); |
| static const char fourofour[] = "fourofour.css"; |
| GoogleString orig_url = StrCat(kTestDomain, fourofour); |
| SetFetchResponse404(orig_url); |
| ValidateNoChanges("trimmable", CssLinkHref(fourofour)); |
| // Ingress task misses on metadata, gets unsuccessful fetch and returns |
| // original unoptimized reference. |
| // Distributed task misses on metadata and original resource fetch, fails its |
| // fetch (404) and writes that back to metadata and original resource, |
| // returning failure. |
| CheckDistributedFetch(0, // successful distributed fetches |
| 1, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| EXPECT_EQ(0, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| |
| ClearStats(); |
| // Try again, this time it should be a quick metadata lookup at the ingress |
| // task. |
| ValidateNoChanges("trimmable", CssLinkHref(fourofour)); |
| CheckDistributedFetch(0, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| } |
| |
| // Similar to RewriteContextTest.TrimDelayed test but modified for distributed |
| // rewrites. |
| TEST_F(DistributedRewriteContextTest, TrimDelayed) { |
| // 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. |
| SetupDistributedTest(); |
| other_factory_->SetupWaitFetcher(); |
| test_distributed_fetcher_.set_blocking_fetch(false); |
| |
| // First time distribute but the external fetch doesn't finish by ingress task |
| // (or deadline task's for that matter) deadline. |
| ValidateNoChanges("trimmable", CssLinkHref("a.css")); |
| CheckDistributedFetch(0, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| |
| // Let the distributed rewriter finish up its fetch and rewrite. |
| OtherCallFetcherCallbacks(); |
| // Let the ingress task finish up as well. |
| rewrite_driver_->WaitForShutDown(); |
| factory_->mock_scheduler()->AwaitQuiescence(); |
| |
| // Now the rewrite is done, make sure the stats look right. |
| // Ingress: same as before |
| // Distributed: puts the original and optimized resource and metadata in |
| // cache. |
| CheckDistributedFetch(1, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 1); // number of rewrites |
| EXPECT_EQ(0, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| |
| // Second time the ingress metadata hits and that's all that's necessary. |
| ClearStats(); |
| ValidateExpected( |
| "trimmable", CssLinkHref("a.css"), |
| CssLinkHref(Encode("", TrimWhitespaceRewriter::kFilterId, "0", |
| "a.css", "css"))); |
| |
| CheckDistributedFetch(0, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| EXPECT_EQ(1, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| } |
| |
| // Copy of the RewriteContextTest.TrimRepeatedNonOptimizable test modified for |
| // distributed rewrites. |
| TEST_F(DistributedRewriteContextTest, TrimRepeatedNonOptimizable) { |
| // Make sure two instances of the same link are handled properly when |
| // optimization fails. |
| |
| SetupDistributedTest(); |
| ValidateNoChanges("notrimmable2", |
| StrCat(CssLinkHref("b.css"), CssLinkHref("b.css"))); |
| // Ingress task misses metadata and distributes rewrite. |
| // Distributed task misses metadata and original resource, inserts |
| // meteadata, optimized, and unoptimized resource. |
| CheckDistributedFetch(1, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 1); // number of rewrites |
| EXPECT_EQ(0, lru_cache()->num_hits()); // partition |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| } |
| |
| // Distribute a .pagespeed. reconstruction. |
| TEST_F(DistributedRewriteContextTest, IngressDistributedRewriteFetch) { |
| SetupDistributedTest(); |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| |
| // Fetch the .pagespeed. resource and ensure that the rewrite was distributed. |
| GoogleString content; |
| ResponseHeaders response_headers; |
| RequestHeaders request_headers; |
| EXPECT_TRUE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| // Content should be optimized. |
| EXPECT_EQ("a", content); |
| |
| // Make sure the TTL is long and the result is cacheable. |
| EXPECT_EQ(Timer::kYearMs, response_headers.cache_ttl_ms()); |
| EXPECT_TRUE(response_headers.IsProxyCacheable()); |
| EXPECT_TRUE(response_headers.IsBrowserCacheable()); |
| |
| CheckDistributedFetch(1, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 1); // number of rewrites |
| |
| // Ingress task distributes. |
| // Rewrite task misses on two HTTP lookups (once for rewritten resource plus |
| // once for original resource) and one metdata lookup. Then inserts original |
| // resource, optimized resource, and metadata. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| |
| // On the second .pagespeed. request the optimized resource should be in the |
| // shared cache. |
| ClearStats(); |
| EXPECT_TRUE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| |
| // Content should be optimized. |
| EXPECT_EQ("a", content); |
| |
| // The ingress url fetcher should not have run. |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(1, counting_distributed_fetcher()->fetch_count()); |
| |
| // Ingress task hits on one HTTP lookup and returns it. |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| } |
| |
| // Test that distribute_fetches=false is honored. |
| TEST_F(DistributedRewriteContextTest, DistributeFetchesDisabled) { |
| SetupDistributedTest(); |
| options()->ClearSignatureForTesting(); |
| options()->set_distribute_fetches(false); |
| server_context()->ComputeSignature(options()); |
| |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| |
| // Fetch the .pagespeed. resource and ensure that the rewrite was not |
| // distributed. |
| GoogleString content; |
| ResponseHeaders response_headers; |
| RequestHeaders request_headers; |
| EXPECT_TRUE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| // Content should be optimized. |
| EXPECT_EQ("a", content); |
| |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_distributed_fetcher()->fetch_count()); |
| |
| // Ingress task misses on two HTTP lookups (rewritten resource plus once for |
| // original resource) and one metadata lookup. Then inserts original resource, |
| // optimized resource, and metadata. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| |
| // On the second .pagespeed. request the optimized resource should be in the |
| // shared cache. |
| ClearStats(); |
| EXPECT_TRUE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| |
| // Content should be optimized. |
| EXPECT_EQ("a", content); |
| |
| // No fetching. |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_distributed_fetcher()->fetch_count()); |
| |
| // Ingress task hits on one HTTP lookup and returns it. |
| EXPECT_EQ(1, lru_cache()->num_hits()); |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(0, http_cache()->cache_inserts()->Get()); |
| } |
| |
| // If the distributed fetcher returns a 404 then that's what should be |
| // returned. |
| TEST_F(DistributedRewriteContextTest, IngressDistributedRewriteNotFoundFetch) { |
| SetupDistributedTest(); |
| GoogleString orig_url = StrCat(kTestDomain, "fourofour.css"); |
| GoogleString encoded_url = |
| Encode(kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", |
| "fourofour.css", "css"); |
| SetFetchResponse404(orig_url); |
| |
| // Fetch the .pagespeed. resource and ensure that the rewrite gets |
| // distributed. |
| GoogleString content; |
| ResponseHeaders response_headers; |
| RequestHeaders request_headers; |
| |
| EXPECT_FALSE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| // Should be a 404 response. |
| EXPECT_EQ(HttpStatus::kNotFound, response_headers.status_code()); |
| |
| // The distributed fetcher should have run once on the ingress task and the |
| // url fetcher should have run once on the rewrite task. The result goes to |
| // shared cache. |
| CheckDistributedFetch(0, // successful distributed fetches |
| 1, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| |
| // Ingress task distributes and streams back the 404. |
| // Rewrite task misses on two HTTP lookups (once for rewritten resource plus |
| // once for original resource) and one metadata lookup. Then inserts 404'd |
| // original resource and metadata. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(2, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(1, http_cache()->cache_inserts()->Get()); |
| |
| // Fetching again causes another reconstruction and therefore another |
| // distributed rewrite, even though we hit the 404 in cache. |
| // |
| // ingress task: distribute because 404, it fails (because 404). |
| // |
| // rewrite task: 2 .pagespeed. misses, 1 metadata hit, 1 http hit, then fetch |
| // again because 404, fetch locally and hit. Return. |
| ClearStats(); |
| EXPECT_FALSE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| CheckDistributedFetch(0, // successful distributed fetches |
| 1, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| |
| EXPECT_EQ(3, lru_cache()->num_hits()); |
| EXPECT_EQ(1, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| } |
| |
| // Simulate distributed fetch failure, which means that we stream back a failed |
| // response. |
| TEST_F(DistributedRewriteContextTest, |
| IngressDistributedRewriteFailFallbackFetch) { |
| SetupDistributedTest(); |
| test_distributed_fetcher()->set_fail_after_headers(true); |
| |
| // Mock the optimized .pagespeed. response from the rewrite task. |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| |
| GoogleString content; |
| ResponseHeaders response_headers; |
| RequestHeaders request_headers; |
| EXPECT_FALSE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| |
| EXPECT_STREQ("", content); |
| |
| // Ingress task distributes, which fails, but pick up original resource from |
| // shared cache. |
| CheckDistributedFetch(0, // successful distributed fetches |
| 1, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 1); // number of rewrites |
| |
| // Ingress task: Distributed rewrite streams but then fails. |
| // Distributed task: Misses http cache, then metadata. Fetches original |
| // (misses in process), writes it, optimizes, writes optimized, and writes |
| // metadata. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| } |
| |
| // Simulate distributed fetch failure due to a bad URL to test PrepareRequest |
| // failure handling. |
| TEST_F(DistributedRewriteContextTest, |
| IngressDistributedRewriteFailPrepareRequest) { |
| SetupDistributedTest(); |
| GoogleString content; |
| StringAsyncFetch async_fetch(rewrite_driver_->request_context(), &content); |
| |
| // Attempt a distributed fetch with a bad URL. PrepareRequest should fail and |
| // the result should be a FetchResource() call. |
| EXPECT_TRUE( |
| DistributeFetch("", TrimWhitespaceRewriter::kFilterId, &async_fetch)); |
| |
| // Tried to distribute but didn't get through PrepareRequest and fell |
| // back to a FetchResource. |
| EXPECT_TRUE(rewrite_driver_->tried_to_distribute_fetch()); |
| CheckDistributedFetch(0, // successful distributed fetches |
| 0, // unsuccessful distributed fetches |
| 0, // number of ingress fetches |
| 0); // number of rewrites |
| } |
| |
| // Simulate distributed fetch failure where the error occurs before headers |
| // complete (such as a connection error to the distributed task). This should |
| // fall back to local processing. |
| TEST_F(DistributedRewriteContextTest, |
| IngressDistributedRewriteEarlyFailFallbackFetch) { |
| SetupDistributedTest(); |
| test_distributed_fetcher()->set_error_before_headers_complete(true); |
| |
| // Mock the optimized .pagespeed. response from the rewrite task. |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| |
| GoogleString content; |
| ResponseHeaders response_headers; |
| RequestHeaders request_headers; |
| EXPECT_TRUE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| |
| // Should be optimized. |
| EXPECT_STREQ("a", content); |
| |
| // .pagespeed. request fails, causing local rewrite. |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ( |
| 0, other_factory_->counting_distributed_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, distributed_rewrite_successes_->Get()); |
| EXPECT_EQ(1, distributed_rewrite_failures_->Get()); |
| EXPECT_EQ(1, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, other_trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, distributed_metadata_failures_->Get()); |
| |
| // Ingress task: Distributed rewrite streams but fails early, resorts to local |
| // rewrite. Misses http cache, then metadata. Fetches original (misses in |
| // process), writes it, optimizes, writes optimized, and writes metadata. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(3, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, http_cache()->cache_hits()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_misses()->Get()); |
| EXPECT_EQ(2, http_cache()->cache_inserts()->Get()); |
| } |
| |
| TEST_F(DistributedRewriteContextTest, ReturnMetadataOnRequest) { |
| // Sends a fetch that asks for metadata in the response headers and checks |
| // that it's in the response. |
| |
| // We need to make distributed_rewrite_servers != "" and set a |
| // distributed_rewrite_key in order to return metadata. |
| options()->set_distributed_rewrite_servers("example.com"); |
| static const char kDistributedKey[] = "1234123"; |
| options()->set_distributed_rewrite_key(kDistributedKey); |
| static const char kCacheFragment[] = "a-cache-fragment"; |
| options()->set_cache_fragment(kCacheFragment); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| GoogleString bad_encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "1", "a.css", "css"); |
| |
| // Note that the .pagespeed. path with metadata request headers do not |
| // check the http cache up front. If they did that and hit they would |
| // not have metadata to return. Therefore the tests below have fewer |
| // cache misses than you might have expected. |
| |
| // The first .pagespeed. request. It should hit the reconstruction path. |
| // We'll miss on the metadata and the input resource. Then fetch once |
| // and put optimized resource, input resource, and metadata in cache. |
| EXPECT_TRUE(FetchValidatedMetadata(kDistributedKey, encoded_url, encoded_url, |
| kGetRequest)); |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| |
| // We should get metadata even though the optimized output is cached. |
| ClearStats(); |
| EXPECT_TRUE(FetchValidatedMetadata(kDistributedKey, encoded_url, encoded_url, |
| kGetRequest)); |
| EXPECT_EQ(2, lru_cache()->num_hits()); // 1 metadata and 1 http |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| // If we use the wrong encoding the metadata + subsequent HTTP cache will hit, |
| // following the fallback path. |
| ClearStats(); |
| EXPECT_TRUE(FetchValidatedMetadata(kDistributedKey, bad_encoded_url, |
| encoded_url, kGetRequest)); |
| // Expect the bad url to miss twice (RewriteDriver::CacheCallback tries |
| // twice). We should then hit the metadata and good http url. |
| EXPECT_EQ(2, lru_cache()->num_hits()); // 1 metadata and 1 http |
| EXPECT_EQ(0, lru_cache()->num_misses()); |
| EXPECT_EQ(0, lru_cache()->num_inserts()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| |
| // If we clear the caches and use the wrong URL it should use the |
| // reconstruction path and return the right URL and the metadata. |
| ClearStats(); |
| lru_cache()->Clear(); |
| http_cache()->Delete(encoded_url, kCacheFragment); |
| EXPECT_TRUE(FetchValidatedMetadata(kDistributedKey, bad_encoded_url, |
| encoded_url, kGetRequest)); |
| // We should fetch once and insert the input, optimized, and metadata into |
| // cache. |
| EXPECT_EQ(0, lru_cache()->num_hits()); |
| EXPECT_EQ(2, lru_cache()->num_misses()); // 1 metadata and 1 http input |
| EXPECT_EQ(3, lru_cache()->num_inserts()); |
| EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| TEST_F(DistributedRewriteContextTest, HeadMetadata) { |
| // Verify that a HEAD request that asks for metadata returns the metadata |
| // but not the content. We don't check cache hit/miss numbers because that |
| // would be redundant with RewriteContextTest.ReturnMetadataOnRequest. |
| |
| // We need to make distributed_rewrite_servers != "" in order to return |
| // metedata. |
| options()->set_distributed_rewrite_servers("example.com"); |
| static const char kDistributedKey[] = "1234123"; |
| options()->set_distributed_rewrite_key(kDistributedKey); |
| static const char kCacheFragment[] = "a-cache-fragment"; |
| options()->set_cache_fragment(kCacheFragment); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| GoogleString bad_encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "1", "a.css", "css"); |
| |
| // Reconstruction path. |
| EXPECT_TRUE(FetchValidatedMetadata(kDistributedKey, encoded_url, encoded_url, |
| kHeadRequest)); |
| |
| // Second fetch, verify that we skip the initial http cache check and do |
| // return metadata. |
| EXPECT_TRUE(FetchValidatedMetadata(kDistributedKey, encoded_url, encoded_url, |
| kHeadRequest)); |
| |
| // Bad .pagespeed. hash but still gets resolved. |
| EXPECT_TRUE(FetchValidatedMetadata(kDistributedKey, bad_encoded_url, |
| encoded_url, kHeadRequest)); |
| |
| // Bad .pagespeed. hash and empty cache but should still reconstruct properly. |
| lru_cache()->Clear(); |
| http_cache()->Delete(encoded_url, kCacheFragment); |
| EXPECT_TRUE(FetchValidatedMetadata(kDistributedKey, bad_encoded_url, |
| encoded_url, kHeadRequest)); |
| } |
| |
| TEST_F(DistributedRewriteContextTest, NoMetadataWithoutRewriteOption) { |
| // Ensure that we don't return metadata if we're not configured |
| // to run with distributed rewrites. |
| static const char kDistributedKey[] = "1234123"; |
| options()->set_distributed_rewrite_key(kDistributedKey); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| |
| // We didn't set rewrite tasks in options, so we shouldn't get any metadata. |
| EXPECT_FALSE(FetchValidatedMetadata(kDistributedKey, encoded_url, encoded_url, |
| kGetRequest)); |
| } |
| |
| TEST_F(DistributedRewriteContextTest, NoMetadataWithoutSettingKey) { |
| // Ensure that we don't return metadata if we're not configured |
| // to run with distributed rewrites. |
| options()->set_distributed_rewrite_servers("example.com"); |
| static const char kDistributedKey[] = "1234123"; |
| // Neglect to set the distributed rewrite key in options. |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| |
| // We didn't set a distributed rewrite key in options, so we shouldn't get any |
| // metadata. |
| EXPECT_FALSE( |
| FetchValidatedMetadata("", encoded_url, encoded_url, kGetRequest)); |
| EXPECT_FALSE(FetchValidatedMetadata(kDistributedKey, encoded_url, encoded_url, |
| kGetRequest)); |
| } |
| |
| TEST_F(DistributedRewriteContextTest, NoMetadataWithBadKeys) { |
| // Ensure that we don't return metadata if we're not configured |
| // to run with distributed rewrites. |
| options()->set_distributed_rewrite_servers("example.com"); |
| static const char kDistributedKey[] = "a1234123"; |
| options()->set_distributed_rewrite_key(kDistributedKey); |
| InitTrimFilters(kRewrittenResource); |
| InitResources(); |
| |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| |
| EXPECT_FALSE( |
| FetchValidatedMetadata("", encoded_url, encoded_url, kGetRequest)); |
| // Changing case doesn't work. |
| EXPECT_FALSE(FetchValidatedMetadata("A1234123", encoded_url, encoded_url, |
| kGetRequest)); |
| // Sanity check that it does work with the correct key. |
| EXPECT_TRUE(FetchValidatedMetadata("a1234123", encoded_url, encoded_url, |
| kGetRequest)); |
| } |
| |
| // If we try to distribute an HTML rewrite for a resource whose URL is too long |
| // we should handle it gracefully. |
| TEST_F(DistributedRewriteContextTest, GracefullyHandleURLTooLong) { |
| SetupDistributedTest(); |
| |
| // Create a long URL that could feasibly exist but cannot be extended into a |
| // .pagespeed. resource. |
| GoogleString long_url = kTestDomain; |
| for (int i = long_url.size(), |
| max_size = options()->max_url_segment_size() - 4; |
| i < max_size; ++i) { |
| long_url += "a"; |
| } |
| long_url = StrCat(long_url, ".css"); |
| |
| SetResponseWithDefaultHeaders(long_url, kContentTypeCss, " hello ", 60); |
| |
| ValidateNoChanges("long_url", CssLinkHref(long_url)); |
| EXPECT_EQ(0, counting_distributed_fetcher()->fetch_count()); |
| EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count()); |
| EXPECT_EQ( |
| 0, other_factory_->counting_distributed_async_fetcher()->fetch_count()); |
| EXPECT_EQ(0, distributed_rewrite_successes_->Get()); |
| // Even though we didn't have a distributed fetch, we do have a distributed |
| // rewrite failure since when prepping for the fetch we failed because the URL |
| // was too long. |
| EXPECT_EQ(1, distributed_rewrite_failures_->Get()); |
| EXPECT_EQ(0, trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, other_trim_filter_->num_rewrites()); |
| EXPECT_EQ(0, distributed_metadata_failures_->Get()); |
| } |
| |
| // The distributed task should recognize if a distributed task can be rate |
| // controlled. |
| TEST_F(DistributedRewriteContextTest, QueueFetchOnDistributedHtmlTask) { |
| SetupDistributedTest(); |
| |
| // Tack on a rate controlling fetcher which does not allow queueing of any |
| // fetches on the distributed task. A distribution of a background rewrite |
| // should be dropped by the rate controller since it can't queue. |
| other_rewrite_driver_->SetSessionFetcher(new RateControllingUrlAsyncFetcher( |
| other_rewrite_driver_->async_fetcher(), |
| 0, // max fetch global queue size |
| 0, // fetches per host outgoing queueing threshold |
| 0, // fetches per host queued request threshold |
| other_server_context_->thread_system(), |
| other_server_context_->statistics())); |
| |
| ValidateNoChanges("trimmable", CssLinkHref("a.css")); |
| EXPECT_EQ(1, counting_distributed_fetcher()->fetch_count()); |
| EXPECT_EQ(0, other_factory_->counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| // The distributed task should recognize that a distributed fetch cannot be rate |
| // controlled. |
| TEST_F(DistributedRewriteContextTest, QueueFetchOnDistributedFetchTask) { |
| SetupDistributedTest(); |
| |
| // Tack on a rate controlling fetcher which does not allow queueing of any |
| // fetches on the distributed task. A distribution of a fetch request should |
| // bypass rate controlling. |
| other_rewrite_driver_->SetSessionFetcher(new RateControllingUrlAsyncFetcher( |
| other_rewrite_driver_->async_fetcher(), |
| 0, // max fetch global queue size |
| 0, // fetches per host outgoing queueing threshold |
| 0, // fetches per host queued request threshold |
| other_server_context_->thread_system(), |
| other_server_context_->statistics())); |
| |
| GoogleString encoded_url = Encode( |
| kTestDomain, TrimWhitespaceRewriter::kFilterId, "0", "a.css", "css"); |
| |
| GoogleString content; |
| ResponseHeaders response_headers; |
| RequestHeaders request_headers; |
| EXPECT_TRUE(FetchResourceUrl(encoded_url, &request_headers, &content, |
| &response_headers)); |
| // Content should be optimized. |
| EXPECT_EQ("a", content); |
| EXPECT_EQ(1, counting_distributed_fetcher()->fetch_count()); |
| EXPECT_EQ(1, other_factory_->counting_url_async_fetcher()->fetch_count()); |
| } |
| |
| } // namespace net_instaweb |