// 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 "base/basictypes.h" #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/location.h" #include "base/single_thread_task_runner.h" #include "base/test/histogram_tester.h" #include "base/thread_task_runner_handle.h" #include "components/history/core/browser/history_constants.h" #include "components/history/core/browser/history_service.h" #include "components/history/core/browser/history_types.h" #include "components/precache/core/precache_switches.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/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" namespace precache { namespace { using ::testing::_; using ::testing::ContainerEq; using ::testing::ElementsAre; using ::testing::Invoke; using ::testing::IsEmpty; using ::testing::Pair; using ::testing::SaveArg; const char kConfigURL[] = "http://config-url.com"; const char kManifestURLPrefix[] = "http://manifest-url-prefix.com/"; const char kGoodManifestURL[] = "http://manifest-url-prefix.com/good-manifest.com"; 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 MockHistoryService : public history::HistoryService { public: MockHistoryService() { ON_CALL(*this, HostRankIfAvailable(_, _)) .WillByDefault(Invoke( [](const GURL& url, const base::Callback& callback) { callback.Run(history::kMaxTopHosts); })); } MOCK_CONST_METHOD2(TopHosts, void(int num_hosts, const TopHostsCallback& callback)); MOCK_CONST_METHOD2(HostRankIfAvailable, void(const GURL& url, const base::Callback& callback)); }; ACTION_P(ReturnHosts, starting_hosts) { arg1.Run(starting_hosts); } class TestPrecacheCompletionCallback { public: TestPrecacheCompletionCallback() : was_on_done_called_(false) {} void OnDone(bool precaching_started) { 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 PrecacheManagerUnderTest : public PrecacheManager { public: PrecacheManagerUnderTest(content::BrowserContext* browser_context, const sync_driver::SyncService* const sync_service, const history::HistoryService* const history_service) : PrecacheManager(browser_context, sync_service, history_service) {} bool ShouldRun() const override { return true; } bool WouldRun() const override { return true; } }; class PrecacheManagerTest : public testing::Test { public: PrecacheManagerTest() : precache_manager_(&browser_context_, nullptr /* sync_service */, &history_service_), factory_(nullptr, base::Bind(&TestURLFetcherCallback::CreateURLFetcher, base::Unretained(&url_callback_))) {} protected: void SetUp() override { base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( switches::kPrecacheConfigSettingsURL, kConfigURL); base::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_; PrecacheManagerUnderTest precache_manager_; TestURLFetcherCallback url_callback_; net::FakeURLFetcherFactory factory_; TestPrecacheCompletionCallback precache_callback_; testing::NiceMock history_service_; base::HistogramTester histograms_; }; TEST_F(PrecacheManagerTest, StartAndFinishPrecaching) { EXPECT_FALSE(precache_manager_.IsPrecaching()); MockHistoryService::TopHostsCallback top_hosts_callback; EXPECT_CALL(history_service_, TopHosts(NumTopHosts(), _)) .WillOnce(SaveArg<1>(&top_hosts_callback)); factory_.SetFakeResponse(GURL(kGoodManifestURL), "", net::HTTP_OK, net::URLRequestStatus::SUCCESS); precache_manager_.StartPrecaching(precache_callback_.GetCallback()); EXPECT_TRUE(precache_manager_.IsPrecaching()); top_hosts_callback.Run( history::TopHostsList(1, std::make_pair("good-manifest.com", 1))); base::MessageLoop::current()->RunUntilIdle(); // For PrecacheFetcher. EXPECT_FALSE(precache_manager_.IsPrecaching()); EXPECT_TRUE(precache_callback_.was_on_done_called()); std::multiset expected_requested_urls; expected_requested_urls.insert(GURL(kConfigURL)); expected_requested_urls.insert(GURL(kGoodManifestURL)); EXPECT_EQ(expected_requested_urls, url_callback_.requested_urls()); } TEST_F(PrecacheManagerTest, StartAndCancelPrecachingBeforeURLsReceived) { EXPECT_FALSE(precache_manager_.IsPrecaching()); MockHistoryService::TopHostsCallback top_hosts_callback; EXPECT_CALL(history_service_, TopHosts(NumTopHosts(), _)) .WillOnce(SaveArg<1>(&top_hosts_callback)); precache_manager_.StartPrecaching(precache_callback_.GetCallback()); EXPECT_TRUE(precache_manager_.IsPrecaching()); precache_manager_.CancelPrecaching(); EXPECT_FALSE(precache_manager_.IsPrecaching()); top_hosts_callback.Run( history::TopHostsList(1, std::make_pair("starting-url.com", 1))); base::MessageLoop::current()->RunUntilIdle(); // For PrecacheFetcher. EXPECT_FALSE(precache_manager_.IsPrecaching()); EXPECT_FALSE(precache_callback_.was_on_done_called()); EXPECT_TRUE(url_callback_.requested_urls().empty()); } TEST_F(PrecacheManagerTest, StartAndCancelPrecachingAfterURLsReceived) { EXPECT_FALSE(precache_manager_.IsPrecaching()); EXPECT_CALL(history_service_, TopHosts(NumTopHosts(), _)) .WillOnce(ReturnHosts( history::TopHostsList(1, std::make_pair("starting-url.com", 1)))); precache_manager_.StartPrecaching(precache_callback_.GetCallback()); // Since the |history_service_| 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(); // For PrecacheFetcher. EXPECT_FALSE(precache_manager_.IsPrecaching()); 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, RecordStatsForFetchWithSizeZero) { // Fetches with size 0 should be ignored. precache_manager_.RecordStatsForFetch(GURL("http://url.com"), GURL(), base::TimeDelta(), base::Time(), 0, false); base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), IsEmpty()); } TEST_F(PrecacheManagerTest, RecordStatsForFetchWithNonHTTP) { // Fetches for URLs with schemes other than HTTP or HTTPS should be ignored. precache_manager_.RecordStatsForFetch(GURL("ftp://ftp.com"), GURL(), base::TimeDelta(), base::Time(), 1000, false); base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), IsEmpty()); } TEST_F(PrecacheManagerTest, RecordStatsForFetchWithEmptyURL) { // Fetches for empty URLs should be ignored. precache_manager_.RecordStatsForFetch(GURL(), GURL(), base::TimeDelta(), base::Time(), 1000, false); base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), IsEmpty()); } TEST_F(PrecacheManagerTest, RecordStatsForFetchDuringPrecaching) { EXPECT_CALL(history_service_, TopHosts(NumTopHosts(), _)) .WillOnce(ReturnHosts(history::TopHostsList())); precache_manager_.StartPrecaching(precache_callback_.GetCallback()); EXPECT_TRUE(precache_manager_.IsPrecaching()); precache_manager_.RecordStatsForFetch(GURL("http://url.com"), GURL(), base::TimeDelta(), base::Time(), 1000, false); precache_manager_.CancelPrecaching(); // For PrecacheFetcher and RecordURLPrecached. base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), ElementsAre(Pair("Precache.DownloadedPrecacheMotivated", 1), Pair("Precache.Fetch.PercentCompleted", 1), Pair("Precache.Fetch.ResponseBytes.Network", 1), Pair("Precache.Fetch.ResponseBytes.Total", 1), Pair("Precache.Latency.Prefetch", 1))); } TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTP) { precache_manager_.RecordStatsForFetch(GURL("http://http-url.com"), GURL(), base::TimeDelta(), base::Time(), 1000, false); base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), ElementsAre(Pair("Precache.DownloadedNonPrecache", 1), Pair("Precache.Latency.NonPrefetch", 1), Pair("Precache.Latency.NonPrefetch.NonTopHosts", 1))); } TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTPS) { precache_manager_.RecordStatsForFetch(GURL("https://https-url.com"), GURL(), base::TimeDelta(), base::Time(), 1000, false); base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), ElementsAre(Pair("Precache.DownloadedNonPrecache", 1), Pair("Precache.Latency.NonPrefetch", 1), Pair("Precache.Latency.NonPrefetch.NonTopHosts", 1))); } TEST_F(PrecacheManagerTest, RecordStatsForFetchInTopHosts) { EXPECT_CALL(history_service_, HostRankIfAvailable(GURL("http://referrer.com"), _)) .WillOnce(Invoke( [](const GURL& url, const base::Callback& callback) { callback.Run(0); })); precache_manager_.RecordStatsForFetch( GURL("http://http-url.com"), GURL("http://referrer.com"), base::TimeDelta(), base::Time(), 1000, false); base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), ElementsAre(Pair("Precache.DownloadedNonPrecache", 1), Pair("Precache.Latency.NonPrefetch", 1), Pair("Precache.Latency.NonPrefetch.TopHosts", 1))); } TEST_F(PrecacheManagerTest, DeleteExpiredPrecacheHistory) { // TODO(twifkak): Split this into multiple tests. base::HistogramTester::CountsMap expected_histogram_count_map; // This test has to use Time::Now() because StartPrecaching uses Time::Now(). const base::Time kCurrentTime = base::Time::Now(); EXPECT_CALL(history_service_, TopHosts(NumTopHosts(), _)) .Times(2) .WillRepeatedly(ReturnHosts(history::TopHostsList())); precache_manager_.StartPrecaching(precache_callback_.GetCallback()); EXPECT_TRUE(precache_manager_.IsPrecaching()); // Precache a bunch of URLs, with different fetch times. precache_manager_.RecordStatsForFetch( GURL("http://old-fetch.com"), GURL(), base::TimeDelta(), kCurrentTime - base::TimeDelta::FromDays(61), 1000, false); precache_manager_.RecordStatsForFetch( GURL("http://recent-fetch.com"), GURL(), base::TimeDelta(), kCurrentTime - base::TimeDelta::FromDays(59), 1000, false); precache_manager_.RecordStatsForFetch( GURL("http://yesterday-fetch.com"), GURL(), base::TimeDelta(), kCurrentTime - base::TimeDelta::FromDays(1), 1000, false); expected_histogram_count_map["Precache.DownloadedPrecacheMotivated"] += 3; expected_histogram_count_map["Precache.Fetch.PercentCompleted"]++; expected_histogram_count_map["Precache.Fetch.ResponseBytes.Network"]++; expected_histogram_count_map["Precache.Fetch.ResponseBytes.Total"]++; expected_histogram_count_map["Precache.Latency.Prefetch"] += 3; precache_manager_.CancelPrecaching(); // For PrecacheFetcher and RecordURLPrecached. base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), ContainerEq(expected_histogram_count_map)); // The expired precache will be deleted during precaching this time. precache_manager_.StartPrecaching(precache_callback_.GetCallback()); EXPECT_TRUE(precache_manager_.IsPrecaching()); expected_histogram_count_map["Precache.Fetch.PercentCompleted"]++; expected_histogram_count_map["Precache.Fetch.ResponseBytes.Network"]++; expected_histogram_count_map["Precache.Fetch.ResponseBytes.Total"]++; precache_manager_.CancelPrecaching(); // For PrecacheFetcher and RecordURLPrecached. 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"), GURL(), base::TimeDelta(), kCurrentTime, 1000, true); expected_histogram_count_map["Precache.Latency.NonPrefetch"]++; expected_histogram_count_map["Precache.Latency.NonPrefetch.NonTopHosts"]++; base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), ContainerEq(expected_histogram_count_map)); // 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"), GURL(), base::TimeDelta(), kCurrentTime, 1000, true); precache_manager_.RecordStatsForFetch(GURL("http://yesterday-fetch.com"), GURL(), base::TimeDelta(), kCurrentTime, 1000, true); expected_histogram_count_map["Precache.Latency.NonPrefetch"] += 2; expected_histogram_count_map["Precache.Latency.NonPrefetch.NonTopHosts"] += 2; expected_histogram_count_map["Precache.Saved"] += 2; base::MessageLoop::current()->RunUntilIdle(); EXPECT_THAT(histograms_.GetTotalCountsForPrefix("Precache."), ContainerEq(expected_histogram_count_map)); } } // namespace } // namespace precache