blob: ea1fb5347bad60b5f97b774df117ed164c84cc77 [file] [log] [blame]
/*
* 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 <vector>
#include "base/logging.h"
#include "net/instaweb/http/public/counting_url_async_fetcher.h"
#include "net/instaweb/http/public/mock_url_fetcher.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_namer.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_rewrite_driver_factory.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/gtest.h"
#include "pagespeed/kernel/base/hasher.h"
#include "pagespeed/kernel/base/mem_file_system.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"
#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/response_headers.h"
namespace net_instaweb {
// Test resource update behavior.
class ResourceUpdateTest : public RewriteContextTestBase {
protected:
static const char kOriginalUrl[];
ResourceUpdateTest() {
FetcherUpdateDateHeaders();
}
// Rewrite supplied HTML, search find rewritten resource URL (EXPECT only 1),
// and return the fetched contents of that resource.
//
// Helper function to specific functions below.
GoogleString RewriteResource(const StringPiece& id,
const GoogleString& html_input) {
// We use MD5 hasher instead of mock hasher so that different resources
// are assigned different URLs.
UseMd5Hasher();
// Rewrite HTML.
Parse(id, html_input);
// Find rewritten resource URL.
StringVector css_urls;
CollectCssLinks(StrCat(id, "-collect"), output_buffer_, &css_urls);
EXPECT_EQ(1UL, css_urls.size());
const GoogleString& rewritten_url = AbsolutifyUrl(css_urls[0]);
// Fetch rewritten resource
return FetchUrlAndCheckHash(rewritten_url);
}
GoogleString FetchUrlAndCheckHash(const StringPiece& url) {
// Fetch resource.
GoogleString contents;
EXPECT_TRUE(FetchResourceUrl(url, &contents));
// Check that hash code is correct.
ResourceNamer namer;
rewrite_driver()->Decode(url, &namer);
EXPECT_EQ(hasher()->Hash(contents), namer.hash());
return contents;
}
// Simulates requesting HTML doc and then loading resource.
GoogleString RewriteSingleResource(const StringPiece& id) {
return RewriteResource(id, CssLinkHref(kOriginalUrl));
}
GoogleString CombineResources(const StringPiece& id) {
return RewriteResource(
id, StrCat(CssLinkHref("web/a.css"), CssLinkHref("file/b.css"),
CssLinkHref("web/c.css"), CssLinkHref("file/d.css")));
}
StringVector RewriteNestedResources(const StringPiece& id) {
// Rewrite everything and fetch the rewritten main resource.
GoogleString rewritten_list = RewriteResource(id, CssLinkHref("main.txt"));
// Parse URLs for subresources.
StringPieceVector urls;
SplitStringPieceToVector(rewritten_list, "\n", &urls, true);
// Load text of subresources.
StringVector subresources;
for (StringPieceVector::const_iterator iter = urls.begin();
iter != urls.end(); ++iter) {
subresources.push_back(FetchUrlAndCheckHash(*iter));
}
return subresources;
}
void ReconfigureNestedFilter(
bool expected_nested_rewrite_result) {
nested_filter_->set_expected_nested_rewrite_result(
expected_nested_rewrite_result);
}
};
const char ResourceUpdateTest::kOriginalUrl[] = "a.css";
// Test to make sure that 404's expire.
TEST_F(ResourceUpdateTest, TestExpire404) {
InitTrimFilters(kRewrittenResource);
// First, set a 404.
SetFetchResponse404(kOriginalUrl);
// Trying to rewrite it should not do anything..
ValidateNoChanges("404", CssLinkHref(kOriginalUrl));
// Now move forward 20 years and upload a new version. We should
// be ready to optimize at that point.
// "And thus Moses wandered the desert for only 20 years, because of a
// limitation in the implementation of time_t."
AdvanceTimeMs(20 * Timer::kYearMs);
SetResponseWithDefaultHeaders(kOriginalUrl, kContentTypeCss, " init ", 100);
EXPECT_EQ("init", RewriteSingleResource("200"));
}
TEST_F(ResourceUpdateTest, OnTheFly) {
InitTrimFilters(kOnTheFlyResource);
int64 ttl_ms = 5 * Timer::kMinuteMs;
// 1) Set first version of resource.
SetResponseWithDefaultHeaders(kOriginalUrl, kContentTypeCss,
" init ", ttl_ms / 1000);
ClearStats();
EXPECT_EQ("init", RewriteSingleResource("first_load"));
// TODO(sligocki): Why are we rewriting twice here?
// EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(2, trim_filter_->num_rewrites());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(0, file_system()->num_input_file_opens());
// 2) Advance time, but not so far that resources have expired.
AdvanceTimeMs(ttl_ms / 2);
ClearStats();
// Rewrite should be the same.
EXPECT_EQ("init", RewriteSingleResource("advance_time"));
EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(0, file_system()->num_input_file_opens());
// 3) Change resource.
SetResponseWithDefaultHeaders(kOriginalUrl, kContentTypeCss,
" new ", ttl_ms / 1000);
ClearStats();
// Rewrite should still be the same, because it's found in cache.
EXPECT_EQ("init", RewriteSingleResource("stale_content"));
EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(0, file_system()->num_input_file_opens());
// 4) Advance time so that old cached input resource expires.
AdvanceTimeMs(ttl_ms);
ClearStats();
// Rewrite should now use new resource.
EXPECT_EQ("new", RewriteSingleResource("updated_content"));
// EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(2, trim_filter_->num_rewrites());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(0, file_system()->num_input_file_opens());
}
TEST_F(ResourceUpdateTest, Rewritten) {
FetcherUpdateDateHeaders();
InitTrimFilters(kRewrittenResource);
int64 ttl_ms = 5 * Timer::kMinuteMs;
// 1) Set first version of resource.
ResponseHeaders response_headers;
response_headers.SetStatusAndReason(HttpStatus::kOK);
response_headers.Add(HttpAttributes::kContentType,
kContentTypeCss.mime_type());
response_headers.Add(HttpAttributes::kEtag, "original");
response_headers.SetDateAndCaching(timer()->NowMs(), ttl_ms);
response_headers.ComputeCaching();
mock_url_fetcher()->SetConditionalResponse(
"http://test.com/a.css", -1, "original", response_headers, " init ");
ClearStats();
EXPECT_EQ("init", RewriteSingleResource("first_load"));
EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(6, counting_url_async_fetcher()->byte_count());
EXPECT_EQ(0, file_system()->num_input_file_opens());
// 2) Advance time, but not so far that resources have expired.
AdvanceTimeMs(ttl_ms / 2);
ClearStats();
// Rewrite should be the same.
EXPECT_EQ("init", RewriteSingleResource("advance_time"));
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, file_system()->num_input_file_opens());
// 3) Change resource.
response_headers.Replace(HttpAttributes::kEtag, "new");
mock_url_fetcher()->SetConditionalResponse(
"http://test.com/a.css", -1, "new", response_headers, " new ");
ClearStats();
// Rewrite should still be the same, because it's found in cache.
EXPECT_EQ("init", RewriteSingleResource("stale_content"));
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, file_system()->num_input_file_opens());
// 4) Advance time so that old cached input resource expires.
AdvanceTimeMs(ttl_ms);
ClearStats();
// Rewrite should now use new resource.
EXPECT_EQ("new", RewriteSingleResource("updated_content"));
EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(5, counting_url_async_fetcher()->byte_count());
EXPECT_EQ(0, file_system()->num_input_file_opens());
// 5) Advance time so that the new input resource expires and is conditionally
// refreshed.
AdvanceTimeMs(2 * ttl_ms);
ClearStats();
// Rewrite should now use new resource.
EXPECT_EQ("new", RewriteSingleResource("updated_content"));
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(
1,
server_context()->rewrite_stats()->num_conditional_refreshes()->Get());
EXPECT_EQ(0, file_system()->num_input_file_opens());
}
TEST_F(ResourceUpdateTest, LoadFromFileOnTheFly) {
options()->file_load_policy()->Associate(kTestDomain, "/test/");
InitTrimFilters(kOnTheFlyResource);
int64 ttl_ms = 5 * Timer::kMinuteMs;
// 1) Set first version of resource.
WriteFile("/test/a.css", " init ");
ClearStats();
EXPECT_EQ("init", RewriteSingleResource("first_load"));
// EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(2, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
// EXPECT_EQ(1, file_system()->num_input_file_opens());
EXPECT_EQ(2, file_system()->num_input_file_opens());
// 2) Advance time, but not so far that resources would have expired if
// they were loaded by UrlFetch.
AdvanceTimeMs(ttl_ms / 2);
ClearStats();
// Rewrite should be the same.
EXPECT_EQ("init", RewriteSingleResource("advance_time"));
EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(1, file_system()->num_input_file_opens());
// 3) Change resource.
WriteFile("/test/a.css", " new ");
ClearStats();
// Rewrite should immediately update.
EXPECT_EQ("new", RewriteSingleResource("updated_content"));
// EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(2, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
// EXPECT_EQ(1, file_system()->num_input_file_opens());
EXPECT_EQ(2, file_system()->num_input_file_opens());
// 4) Advance time so that old cached input resource expires.
AdvanceTimeMs(ttl_ms);
ClearStats();
// Rewrite should now use new resource.
EXPECT_EQ("new", RewriteSingleResource("updated_content"));
EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(1, file_system()->num_input_file_opens());
}
TEST_F(ResourceUpdateTest, LoadFromFileRewritten) {
options()->file_load_policy()->Associate(kTestDomain, "/test/");
InitTrimFilters(kRewrittenResource);
int64 ttl_ms = 5 * Timer::kMinuteMs;
// 1) Set first version of resource.
WriteFile("/test/a.css", " init ");
ClearStats();
EXPECT_EQ("init", RewriteSingleResource("first_load"));
EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(1, file_system()->num_input_file_opens());
// 2) Advance time, but not so far that resources would have expired if
// they were loaded by UrlFetch.
AdvanceTimeMs(ttl_ms / 2);
ClearStats();
// Rewrite should be the same.
EXPECT_EQ("init", RewriteSingleResource("advance_time"));
EXPECT_EQ(0, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(0, file_system()->num_input_file_opens());
// 3) Change resource.
WriteFile("/test/a.css", " new ");
ClearStats();
// Rewrite should immediately update.
EXPECT_EQ("new", RewriteSingleResource("updated_content"));
EXPECT_EQ(1, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(1, file_system()->num_input_file_opens());
// 4) Advance time so that old cached input resource expires.
AdvanceTimeMs(ttl_ms);
ClearStats();
// Rewrite should now use new resource.
EXPECT_EQ("new", RewriteSingleResource("updated_content"));
EXPECT_EQ(0, trim_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(0, file_system()->num_input_file_opens());
}
class CombineResourceUpdateTest : public ResourceUpdateTest {
protected:
};
TEST_F(CombineResourceUpdateTest, CombineDifferentTTLs) {
// Initialize system.
InitCombiningFilter(0);
options()->file_load_policy()->Associate("http://test.com/file/", "/test/");
// Initialize resources.
int64 kLongTtlMs = 1 * Timer::kMonthMs;
int64 kShortTtlMs = 1 * Timer::kMinuteMs;
SetResponseWithDefaultHeaders("http://test.com/web/a.css", kContentTypeCss,
" a1 ", kLongTtlMs / 1000);
WriteFile("/test/b.css", " b1 ");
SetResponseWithDefaultHeaders("http://test.com/web/c.css", kContentTypeCss,
" c1 ", kShortTtlMs / 1000);
WriteFile("/test/d.css", " d1 ");
// 1) Initial combined resource.
EXPECT_EQ(" a1 b1 c1 d1 ", CombineResources("first_load"));
EXPECT_EQ(1, combining_filter_->num_rewrites());
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(2, file_system()->num_input_file_opens());
// Note that we stat each file as we load it in.
EXPECT_EQ(2, file_system()->num_input_file_stats());
ClearStats();
// 2) Advance time, but not so far that any resources have expired.
AdvanceTimeMs(kShortTtlMs / 2);
// Rewrite should be the same.
EXPECT_EQ(" a1 b1 c1 d1 ", CombineResources("advance_time"));
EXPECT_EQ(0, combining_filter_->num_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(0, file_system()->num_input_file_opens());
EXPECT_EQ(2, file_system()->num_input_file_stats());
ClearStats();
// 3) Change resources
SetResponseWithDefaultHeaders("http://test.com/web/a.css", kContentTypeCss,
" a2 ", kLongTtlMs / 1000);
WriteFile("/test/b.css", " b2 ");
SetResponseWithDefaultHeaders("http://test.com/web/c.css", kContentTypeCss,
" c2 ", kShortTtlMs / 1000);
WriteFile("/test/d.css", " d2 ");
// File-based resources should be updated, but web-based ones still cached.
EXPECT_EQ(" a1 b2 c1 d2 ", CombineResources("stale_content"));
EXPECT_EQ(1, combining_filter_->num_rewrites()); // Because inputs updated.
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
EXPECT_EQ(2, file_system()->num_input_file_opens()); // Read both files.
// 2 reads + stat of b
EXPECT_EQ(3, file_system()->num_input_file_stats());
ClearStats();
// 4) Advance time so that short-cached input expires.
AdvanceTimeMs(kShortTtlMs);
// All but long TTL UrlInputResource should be updated.
EXPECT_EQ(" a1 b2 c2 d2 ", CombineResources("short_updated"));
EXPECT_EQ(1, combining_filter_->num_rewrites()); // Because inputs updated.
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); // One expired.
EXPECT_EQ(2, file_system()->num_input_file_opens()); // Re-read files.
// 2 file reads + stat of b, which we get to as a has long TTL,
// as well as as of d (for figuring out revalidation strategy).
EXPECT_EQ(4, file_system()->num_input_file_stats());
ClearStats();
// 5) Advance time so that all inputs have expired and been updated.
AdvanceTimeMs(kLongTtlMs);
// Rewrite should now use all new resources.
EXPECT_EQ(" a2 b2 c2 d2 ", CombineResources("all_updated"));
EXPECT_EQ(1, combining_filter_->num_rewrites()); // Because inputs updated.
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); // Both expired.
EXPECT_EQ(2, file_system()->num_input_file_opens()); // Re-read files.
// 2 read-induced stats, 2 stats to figure out how to deal with
// c + d for invalidation.
EXPECT_EQ(4, file_system()->num_input_file_stats());
ClearStats();
}
TEST_F(ResourceUpdateTest, NestedTestExpireNested404) {
UseMd5Hasher();
InitNestedFilter(NestedFilter::kExpectNestedRewritesFail);
const int64 kDecadeMs = 10 * Timer::kYearMs;
// Have the nested one have a 404...
const GoogleString kOutUrl = Encode("", "nf", "sdUklQf3sx",
"main.txt", "css");
SetResponseWithDefaultHeaders("http://test.com/main.txt", kContentTypeCss,
"a.css\n", 4 * kDecadeMs / 1000);
SetFetchResponse404("a.css");
ValidateExpected("nested_404", CssLinkHref("main.txt"), CssLinkHref(kOutUrl));
GoogleString contents;
EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, kOutUrl), &contents));
EXPECT_EQ("http://test.com/a.css\n", contents);
// Determine if we're using the TestUrlNamer, for the hash later.
CHECK(!factory_->use_test_url_namer());
// Now move forward two decades, and upload a new version. We should
// be ready to optimize at that point, but input should not be expired.
AdvanceTimeMs(2 * kDecadeMs);
SetResponseWithDefaultHeaders("a.css", kContentTypeCss, " lowercase ", 100);
ReconfigureNestedFilter(NestedFilter::kExpectNestedRewritesSucceed);
const GoogleString kFullOutUrl =
Encode("", "nf", "G60oQsKZ9F", "main.txt", "css");
const GoogleString kInnerUrl = StrCat(Encode("", "uc", "N4LKMOq9ms",
"a.css", "css"), "\n");
ValidateExpected("nested_404", CssLinkHref("main.txt"),
CssLinkHref(kFullOutUrl));
EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, kFullOutUrl), &contents));
EXPECT_EQ(StrCat(kTestDomain, kInnerUrl), contents);
EXPECT_TRUE(FetchResourceUrl(StrCat(kTestDomain, kInnerUrl), &contents));
EXPECT_EQ(" LOWERCASE ", contents);
}
TEST_F(ResourceUpdateTest, NestedDifferentTTLs) {
// Initialize system.
InitNestedFilter(NestedFilter::kExpectNestedRewritesSucceed);
options()->file_load_policy()->Associate("http://test.com/file/", "/test/");
// Initialize resources.
const int64 kExtraLongTtlMs = 10 * Timer::kMonthMs;
const int64 kLongTtlMs = 1 * Timer::kMonthMs;
const int64 kShortTtlMs = 1 * Timer::kMinuteMs;
SetResponseWithDefaultHeaders("http://test.com/main.txt", kContentTypeCss,
"web/a.css\n"
"file/b.css\n"
"web/c.css\n", kExtraLongTtlMs / 1000);
SetResponseWithDefaultHeaders("http://test.com/web/a.css", kContentTypeCss,
" a1 ", kLongTtlMs / 1000);
WriteFile("/test/b.css", " b1 ");
SetResponseWithDefaultHeaders("http://test.com/web/c.css", kContentTypeCss,
" c1 ", kShortTtlMs / 1000);
ClearStats();
// 1) Initial combined resource.
StringVector result_vector;
result_vector = RewriteNestedResources("first_load");
ASSERT_EQ(3, result_vector.size());
EXPECT_EQ(" A1 ", result_vector[0]);
EXPECT_EQ(" B1 ", result_vector[1]);
EXPECT_EQ(" C1 ", result_vector[2]);
EXPECT_EQ(1, nested_filter_->num_top_rewrites());
// 3 nested rewrites during actual rewrite, 3 when redoing them for
// on-the-fly when checking the output.
EXPECT_EQ(6, nested_filter_->num_sub_rewrites());
EXPECT_EQ(3, counting_url_async_fetcher()->fetch_count());
// b.css, twice (rewrite and fetch)
EXPECT_EQ(2, file_system()->num_input_file_opens());
// b.css twice, again.
EXPECT_EQ(2, file_system()->num_input_file_stats());
ClearStats();
// 2) Advance time, but not so far that any resources have expired.
AdvanceTimeMs(kShortTtlMs / 2);
// Rewrite should be the same.
result_vector = RewriteNestedResources("advance_time");
ASSERT_EQ(3, result_vector.size());
EXPECT_EQ(" A1 ", result_vector[0]);
EXPECT_EQ(" B1 ", result_vector[1]);
EXPECT_EQ(" C1 ", result_vector[2]);
EXPECT_EQ(0, nested_filter_->num_top_rewrites());
EXPECT_EQ(3, nested_filter_->num_sub_rewrites()); // on inner fetch.
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
// b on rewrite.
EXPECT_EQ(1, file_system()->num_input_file_opens());
// re-check of b, b on rewrite.
EXPECT_EQ(2, file_system()->num_input_file_stats());
ClearStats();
// 3) Change resources
SetResponseWithDefaultHeaders("http://test.com/web/a.css", kContentTypeCss,
" a2 ", kLongTtlMs / 1000);
WriteFile("/test/b.css", " b2 ");
SetResponseWithDefaultHeaders("http://test.com/web/c.css", kContentTypeCss,
" c2 ", kShortTtlMs / 1000);
// File-based resources should be updated, but web-based ones still cached.
result_vector = RewriteNestedResources("stale_content");
ASSERT_EQ(3, result_vector.size());
EXPECT_EQ(" A1 ", result_vector[0]);
EXPECT_EQ(" B2 ", result_vector[1]);
EXPECT_EQ(" C1 ", result_vector[2]);
EXPECT_EQ(1, nested_filter_->num_top_rewrites()); // Because inputs updated
// on rewrite, b.css; when checking inside RewriteNestedResources, all 3
// got rewritten.
EXPECT_EQ(4, nested_filter_->num_sub_rewrites());
EXPECT_EQ(0, counting_url_async_fetcher()->fetch_count());
// b.css, b.css.pagespeed.nf.HASH.css
EXPECT_EQ(2, file_system()->num_input_file_opens());
// The stats here are:
// 1) Stat b.css to figure out if top-level rewrite is valid.
// 2) Stats of the 3 inputs when doing on-the-fly rewriting when
// responding to fetches of inner stuff.
EXPECT_EQ(4, file_system()->num_input_file_stats());
ClearStats();
// 4) Advance time so that short-cached input expires.
AdvanceTimeMs(kShortTtlMs);
// All but long TTL UrlInputResource should be updated.
result_vector = RewriteNestedResources("short_updated");
ASSERT_EQ(3, result_vector.size());
EXPECT_EQ(" A1 ", result_vector[0]);
EXPECT_EQ(" B2 ", result_vector[1]);
EXPECT_EQ(" C2 ", result_vector[2]);
EXPECT_EQ(1, nested_filter_->num_top_rewrites()); // Because inputs updated
EXPECT_EQ(4, nested_filter_->num_sub_rewrites()); // c.css + check fetches
EXPECT_EQ(1, counting_url_async_fetcher()->fetch_count()); // c.css
// b.css
EXPECT_EQ(1, file_system()->num_input_file_opens());
// b.css, when rewriting outer and fetching inner
EXPECT_EQ(2, file_system()->num_input_file_stats());
ClearStats();
// 5) Advance time so that all inputs have expired and been updated.
AdvanceTimeMs(kLongTtlMs);
// Rewrite should now use all new resources.
result_vector = RewriteNestedResources("short_updated");
ASSERT_EQ(3, result_vector.size());
EXPECT_EQ(" A2 ", result_vector[0]);
EXPECT_EQ(" B2 ", result_vector[1]);
EXPECT_EQ(" C2 ", result_vector[2]);
EXPECT_EQ(1, nested_filter_->num_top_rewrites()); // Because inputs updated
// For rewrite of top-level, we re-do a.css (actually changed) and c.css
// (as it's expired, and we don't check if it's really changed for on-the-fly
// filters). Then there are 3 when we actually fetch them individually.
EXPECT_EQ(5, nested_filter_->num_sub_rewrites());
EXPECT_EQ(2, counting_url_async_fetcher()->fetch_count()); // a.css, c.css
EXPECT_EQ(1, file_system()->num_input_file_opens());
EXPECT_EQ(2, file_system()->num_input_file_stats());
ClearStats();
}
} // namespace net_instaweb