// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/precache/content/precache_manager.h" #include #include #include #include #include "base/basictypes.h" #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/message_loop/message_loop.h" #include "base/metrics/histogram.h" #include "base/metrics/histogram_samples.h" #include "base/metrics/statistics_recorder.h" #include "components/precache/core/precache_switches.h" #include "components/precache/core/url_list_provider.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_browser_thread_bundle.h" #include "net/http/http_status_code.h" #include "net/url_request/test_url_fetcher_factory.h" #include "net/url_request/url_request_status.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" namespace precache { namespace { // A map of histogram names to the total sample counts. typedef std::map HistogramCountMap; const char kConfigURL[] = "http://config-url.com"; const char kManifestURLPrefix[] = "http://manifest-url-prefix.com/"; base::HistogramBase::Count GetHistogramTotalCount(const char* histogram_name) { base::HistogramBase* histogram = base::StatisticsRecorder::FindHistogram(histogram_name); return histogram ? histogram->SnapshotSamples()->TotalCount() : 0; } HistogramCountMap GetHistogramCountMap() { // Note that the PrecacheManager tests don't care about the ".Cellular" // histograms. const char* kHistogramNames[] = {"Precache.DownloadedPrecacheMotivated", "Precache.DownloadedNonPrecache", "Precache.Saved"}; HistogramCountMap histogram_count_map; for (size_t i = 0; i < arraysize(kHistogramNames); ++i) { histogram_count_map[kHistogramNames[i]] = GetHistogramTotalCount(kHistogramNames[i]); } return histogram_count_map; } class TestURLFetcherCallback { public: scoped_ptr CreateURLFetcher( const GURL& url, net::URLFetcherDelegate* delegate, const std::string& response_data, net::HttpStatusCode response_code, net::URLRequestStatus::Status status) { scoped_ptr fetcher(new net::FakeURLFetcher( url, delegate, response_data, response_code, status)); requested_urls_.insert(url); return fetcher.Pass(); } const std::multiset& requested_urls() const { return requested_urls_; } private: // Multiset with one entry for each URL requested. std::multiset requested_urls_; }; class FakeURLListProvider : public URLListProvider { public: FakeURLListProvider(const std::list& urls, bool run_immediately) : urls_(urls), run_immediately_(run_immediately), was_get_urls_called_(false) {} virtual void GetURLs(const GetURLsCallback& callback) OVERRIDE { was_get_urls_called_ = true; if (run_immediately_) { callback.Run(urls_); } else { // Post the callback to be run later in the message loop. base::MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, urls_)); } } bool was_get_urls_called() const { return was_get_urls_called_; } private: const std::list urls_; const bool run_immediately_; bool was_get_urls_called_; }; class TestPrecacheCompletionCallback { public: TestPrecacheCompletionCallback() : was_on_done_called_(false) {} void OnDone() { was_on_done_called_ = true; } PrecacheManager::PrecacheCompletionCallback GetCallback() { return base::Bind(&TestPrecacheCompletionCallback::OnDone, base::Unretained(this)); } bool was_on_done_called() const { return was_on_done_called_; } private: bool was_on_done_called_; }; class PrecacheManagerTest : public testing::Test { public: PrecacheManagerTest() : precache_manager_(&browser_context_), factory_(NULL, base::Bind(&TestURLFetcherCallback::CreateURLFetcher, base::Unretained(&url_callback_))) {} protected: virtual void SetUp() OVERRIDE { base::StatisticsRecorder::Initialize(); CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kPrecacheConfigSettingsURL, kConfigURL); CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kPrecacheManifestURLPrefix, kManifestURLPrefix); // Make the fetch of the precache configuration settings fail. Precaching // should still complete normally in this case. factory_.SetFakeResponse(GURL(kConfigURL), "", net::HTTP_INTERNAL_SERVER_ERROR, net::URLRequestStatus::FAILED); } content::TestBrowserThreadBundle test_browser_thread_bundle_; content::TestBrowserContext browser_context_; PrecacheManager precache_manager_; TestURLFetcherCallback url_callback_; net::FakeURLFetcherFactory factory_; TestPrecacheCompletionCallback precache_callback_; }; TEST_F(PrecacheManagerTest, StartAndFinishPrecaching) { EXPECT_FALSE(precache_manager_.IsPrecaching()); FakeURLListProvider url_list_provider( std::list(1, GURL("http://starting-url.com")), false); precache_manager_.StartPrecaching(precache_callback_.GetCallback(), &url_list_provider); EXPECT_TRUE(precache_manager_.IsPrecaching()); base::MessageLoop::current()->RunUntilIdle(); EXPECT_FALSE(precache_manager_.IsPrecaching()); EXPECT_TRUE(url_list_provider.was_get_urls_called()); EXPECT_TRUE(precache_callback_.was_on_done_called()); std::multiset expected_requested_urls; expected_requested_urls.insert(GURL(kConfigURL)); EXPECT_EQ(expected_requested_urls, url_callback_.requested_urls()); } TEST_F(PrecacheManagerTest, StartAndCancelPrecachingBeforeURLsReceived) { EXPECT_FALSE(precache_manager_.IsPrecaching()); FakeURLListProvider url_list_provider( std::list(1, GURL("http://starting-url.com")), false); precache_manager_.StartPrecaching(precache_callback_.GetCallback(), &url_list_provider); EXPECT_TRUE(precache_manager_.IsPrecaching()); precache_manager_.CancelPrecaching(); EXPECT_FALSE(precache_manager_.IsPrecaching()); base::MessageLoop::current()->RunUntilIdle(); EXPECT_FALSE(precache_manager_.IsPrecaching()); EXPECT_TRUE(url_list_provider.was_get_urls_called()); EXPECT_FALSE(precache_callback_.was_on_done_called()); EXPECT_TRUE(url_callback_.requested_urls().empty()); } TEST_F(PrecacheManagerTest, StartAndCancelPrecachingAfterURLsReceived) { EXPECT_FALSE(precache_manager_.IsPrecaching()); FakeURLListProvider url_list_provider( std::list(1, GURL("http://starting-url.com")), true); precache_manager_.StartPrecaching(precache_callback_.GetCallback(), &url_list_provider); // Since the |url_list_provider| ran the callback immediately, Start() has // been called on the PrecacheFetcher, and the precache config settings have // been requested. The response has not yet been received though, so // precaching is still in progress. EXPECT_TRUE(precache_manager_.IsPrecaching()); precache_manager_.CancelPrecaching(); EXPECT_FALSE(precache_manager_.IsPrecaching()); base::MessageLoop::current()->RunUntilIdle(); EXPECT_FALSE(precache_manager_.IsPrecaching()); EXPECT_TRUE(url_list_provider.was_get_urls_called()); EXPECT_FALSE(precache_callback_.was_on_done_called()); // Even though the response for the precache config settings should not have // been received, the request should still have been made. std::multiset expected_requested_urls; expected_requested_urls.insert(GURL(kConfigURL)); EXPECT_EQ(expected_requested_urls, url_callback_.requested_urls()); } TEST_F(PrecacheManagerTest, RecordStatsForFetchWithIrrelevantFetches) { HistogramCountMap expected_histogram_count_map = GetHistogramCountMap(); // Fetches with size 0 should be ignored. precache_manager_.RecordStatsForFetch(GURL("http://url.com"), base::Time(), 0, false); base::MessageLoop::current()->RunUntilIdle(); EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap()); // Fetches for URLs with schemes other than HTTP or HTTPS should be ignored. precache_manager_.RecordStatsForFetch(GURL("ftp://ftp.com"), base::Time(), 1000, false); base::MessageLoop::current()->RunUntilIdle(); EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap()); // Fetches for empty URLs should be ignored. precache_manager_.RecordStatsForFetch(GURL(), base::Time(), 1000, false); base::MessageLoop::current()->RunUntilIdle(); EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap()); } TEST_F(PrecacheManagerTest, RecordStatsForFetchDuringPrecaching) { HistogramCountMap expected_histogram_count_map = GetHistogramCountMap(); FakeURLListProvider url_list_provider(std::list(), false); precache_manager_.StartPrecaching(precache_callback_.GetCallback(), &url_list_provider); EXPECT_TRUE(precache_manager_.IsPrecaching()); precache_manager_.RecordStatsForFetch(GURL("http://url.com"), base::Time(), 1000, false); precache_manager_.CancelPrecaching(); base::MessageLoop::current()->RunUntilIdle(); expected_histogram_count_map["Precache.DownloadedPrecacheMotivated"]++; EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap()); } TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTP) { HistogramCountMap expected_histogram_count_map = GetHistogramCountMap(); precache_manager_.RecordStatsForFetch(GURL("http://http-url.com"), base::Time(), 1000, false); base::MessageLoop::current()->RunUntilIdle(); expected_histogram_count_map["Precache.DownloadedNonPrecache"]++; EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap()); } TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTPS) { HistogramCountMap expected_histogram_count_map = GetHistogramCountMap(); precache_manager_.RecordStatsForFetch(GURL("https://https-url.com"), base::Time(), 1000, false); base::MessageLoop::current()->RunUntilIdle(); expected_histogram_count_map["Precache.DownloadedNonPrecache"]++; EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap()); } TEST_F(PrecacheManagerTest, DeleteExpiredPrecacheHistory) { // This test has to use Time::Now() because StartPrecaching uses Time::Now(). const base::Time kCurrentTime = base::Time::Now(); HistogramCountMap expected_histogram_count_map = GetHistogramCountMap(); FakeURLListProvider url_list_provider(std::list(), false); precache_manager_.StartPrecaching(precache_callback_.GetCallback(), &url_list_provider); EXPECT_TRUE(precache_manager_.IsPrecaching()); // Precache a bunch of URLs, with different fetch times. precache_manager_.RecordStatsForFetch( GURL("http://old-fetch.com"), kCurrentTime - base::TimeDelta::FromDays(61), 1000, false); precache_manager_.RecordStatsForFetch( GURL("http://recent-fetch.com"), kCurrentTime - base::TimeDelta::FromDays(59), 1000, false); precache_manager_.RecordStatsForFetch( GURL("http://yesterday-fetch.com"), kCurrentTime - base::TimeDelta::FromDays(1), 1000, false); expected_histogram_count_map["Precache.DownloadedPrecacheMotivated"] += 3; precache_manager_.CancelPrecaching(); base::MessageLoop::current()->RunUntilIdle(); EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap()); // The expired precache will be deleted during precaching this time. precache_manager_.StartPrecaching(precache_callback_.GetCallback(), &url_list_provider); EXPECT_TRUE(precache_manager_.IsPrecaching()); precache_manager_.CancelPrecaching(); base::MessageLoop::current()->RunUntilIdle(); EXPECT_FALSE(precache_manager_.IsPrecaching()); // A fetch for the same URL as the expired precache was served from the cache, // but it isn't reported as saved bytes because it had expired in the precache // history. precache_manager_.RecordStatsForFetch( GURL("http://old-fetch.com"), kCurrentTime, 1000, true); base::MessageLoop::current()->RunUntilIdle(); EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap()); // The other precaches should not have expired, so the following fetches from // the cache should count as saved bytes. precache_manager_.RecordStatsForFetch( GURL("http://recent-fetch.com"), kCurrentTime, 1000, true); precache_manager_.RecordStatsForFetch( GURL("http://yesterday-fetch.com"), kCurrentTime, 1000, true); expected_histogram_count_map["Precache.Saved"] += 2; base::MessageLoop::current()->RunUntilIdle(); EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap()); } } // namespace } // namespace precache