Home | History | Annotate | Download | only in content
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "components/precache/content/precache_manager.h"
      6 
      7 #include <list>
      8 #include <map>
      9 #include <set>
     10 #include <string>
     11 
     12 #include "base/basictypes.h"
     13 #include "base/bind.h"
     14 #include "base/callback.h"
     15 #include "base/command_line.h"
     16 #include "base/compiler_specific.h"
     17 #include "base/message_loop/message_loop.h"
     18 #include "base/metrics/histogram.h"
     19 #include "base/metrics/histogram_samples.h"
     20 #include "base/metrics/statistics_recorder.h"
     21 #include "components/precache/core/precache_switches.h"
     22 #include "components/precache/core/url_list_provider.h"
     23 #include "content/public/test/test_browser_context.h"
     24 #include "content/public/test/test_browser_thread_bundle.h"
     25 #include "net/http/http_status_code.h"
     26 #include "net/url_request/test_url_fetcher_factory.h"
     27 #include "net/url_request/url_request_status.h"
     28 #include "net/url_request/url_request_test_util.h"
     29 #include "testing/gtest/include/gtest/gtest.h"
     30 #include "url/gurl.h"
     31 
     32 namespace precache {
     33 
     34 namespace {
     35 
     36 // A map of histogram names to the total sample counts.
     37 typedef std::map<std::string, base::HistogramBase::Count> HistogramCountMap;
     38 
     39 const char kConfigURL[] = "http://config-url.com";
     40 const char kManifestURLPrefix[] = "http://manifest-url-prefix.com/";
     41 
     42 base::HistogramBase::Count GetHistogramTotalCount(const char* histogram_name) {
     43   base::HistogramBase* histogram =
     44       base::StatisticsRecorder::FindHistogram(histogram_name);
     45   return histogram ? histogram->SnapshotSamples()->TotalCount() : 0;
     46 }
     47 
     48 HistogramCountMap GetHistogramCountMap() {
     49   // Note that the PrecacheManager tests don't care about the ".Cellular"
     50   // histograms.
     51   const char* kHistogramNames[] = {"Precache.DownloadedPrecacheMotivated",
     52                                    "Precache.DownloadedNonPrecache",
     53                                    "Precache.Saved"};
     54 
     55   HistogramCountMap histogram_count_map;
     56   for (size_t i = 0; i < arraysize(kHistogramNames); ++i) {
     57     histogram_count_map[kHistogramNames[i]] =
     58         GetHistogramTotalCount(kHistogramNames[i]);
     59   }
     60   return histogram_count_map;
     61 }
     62 
     63 class TestURLFetcherCallback {
     64  public:
     65   scoped_ptr<net::FakeURLFetcher> CreateURLFetcher(
     66       const GURL& url, net::URLFetcherDelegate* delegate,
     67       const std::string& response_data, net::HttpStatusCode response_code,
     68       net::URLRequestStatus::Status status) {
     69     scoped_ptr<net::FakeURLFetcher> fetcher(new net::FakeURLFetcher(
     70         url, delegate, response_data, response_code, status));
     71 
     72     requested_urls_.insert(url);
     73     return fetcher.Pass();
     74   }
     75 
     76   const std::multiset<GURL>& requested_urls() const {
     77     return requested_urls_;
     78   }
     79 
     80  private:
     81   // Multiset with one entry for each URL requested.
     82   std::multiset<GURL> requested_urls_;
     83 };
     84 
     85 class FakeURLListProvider : public URLListProvider {
     86  public:
     87   FakeURLListProvider(const std::list<GURL>& urls, bool run_immediately)
     88       : urls_(urls),
     89         run_immediately_(run_immediately),
     90         was_get_urls_called_(false) {}
     91 
     92   virtual void GetURLs(const GetURLsCallback& callback) OVERRIDE {
     93     was_get_urls_called_ = true;
     94 
     95     if (run_immediately_) {
     96       callback.Run(urls_);
     97     } else {
     98       // Post the callback to be run later in the message loop.
     99       base::MessageLoop::current()->PostTask(FROM_HERE,
    100                                              base::Bind(callback, urls_));
    101     }
    102   }
    103 
    104   bool was_get_urls_called() const {
    105     return was_get_urls_called_;
    106   }
    107 
    108  private:
    109   const std::list<GURL> urls_;
    110   const bool run_immediately_;
    111   bool was_get_urls_called_;
    112 };
    113 
    114 class TestPrecacheCompletionCallback {
    115  public:
    116   TestPrecacheCompletionCallback() : was_on_done_called_(false) {}
    117 
    118   void OnDone() {
    119     was_on_done_called_ = true;
    120   }
    121 
    122   PrecacheManager::PrecacheCompletionCallback GetCallback() {
    123     return base::Bind(&TestPrecacheCompletionCallback::OnDone,
    124                       base::Unretained(this));
    125   }
    126 
    127   bool was_on_done_called() const {
    128     return was_on_done_called_;
    129   }
    130 
    131  private:
    132   bool was_on_done_called_;
    133 };
    134 
    135 class PrecacheManagerTest : public testing::Test {
    136  public:
    137   PrecacheManagerTest()
    138       : precache_manager_(&browser_context_),
    139         factory_(NULL, base::Bind(&TestURLFetcherCallback::CreateURLFetcher,
    140                                   base::Unretained(&url_callback_))) {}
    141 
    142  protected:
    143   virtual void SetUp() OVERRIDE {
    144     base::StatisticsRecorder::Initialize();
    145 
    146     CommandLine::ForCurrentProcess()->AppendSwitchASCII(
    147         switches::kPrecacheConfigSettingsURL, kConfigURL);
    148     CommandLine::ForCurrentProcess()->AppendSwitchASCII(
    149         switches::kPrecacheManifestURLPrefix, kManifestURLPrefix);
    150 
    151     // Make the fetch of the precache configuration settings fail. Precaching
    152     // should still complete normally in this case.
    153     factory_.SetFakeResponse(GURL(kConfigURL), "",
    154                              net::HTTP_INTERNAL_SERVER_ERROR,
    155                              net::URLRequestStatus::FAILED);
    156   }
    157 
    158   content::TestBrowserThreadBundle test_browser_thread_bundle_;
    159   content::TestBrowserContext browser_context_;
    160   PrecacheManager precache_manager_;
    161   TestURLFetcherCallback url_callback_;
    162   net::FakeURLFetcherFactory factory_;
    163   TestPrecacheCompletionCallback precache_callback_;
    164 };
    165 
    166 TEST_F(PrecacheManagerTest, StartAndFinishPrecaching) {
    167   EXPECT_FALSE(precache_manager_.IsPrecaching());
    168 
    169   FakeURLListProvider url_list_provider(
    170       std::list<GURL>(1, GURL("http://starting-url.com")), false);
    171   precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
    172                                     &url_list_provider);
    173 
    174   EXPECT_TRUE(precache_manager_.IsPrecaching());
    175 
    176   base::MessageLoop::current()->RunUntilIdle();
    177   EXPECT_FALSE(precache_manager_.IsPrecaching());
    178   EXPECT_TRUE(url_list_provider.was_get_urls_called());
    179   EXPECT_TRUE(precache_callback_.was_on_done_called());
    180 
    181   std::multiset<GURL> expected_requested_urls;
    182   expected_requested_urls.insert(GURL(kConfigURL));
    183   EXPECT_EQ(expected_requested_urls, url_callback_.requested_urls());
    184 }
    185 
    186 TEST_F(PrecacheManagerTest, StartAndCancelPrecachingBeforeURLsReceived) {
    187   EXPECT_FALSE(precache_manager_.IsPrecaching());
    188 
    189   FakeURLListProvider url_list_provider(
    190       std::list<GURL>(1, GURL("http://starting-url.com")), false);
    191 
    192   precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
    193                                     &url_list_provider);
    194   EXPECT_TRUE(precache_manager_.IsPrecaching());
    195 
    196   precache_manager_.CancelPrecaching();
    197   EXPECT_FALSE(precache_manager_.IsPrecaching());
    198 
    199   base::MessageLoop::current()->RunUntilIdle();
    200   EXPECT_FALSE(precache_manager_.IsPrecaching());
    201   EXPECT_TRUE(url_list_provider.was_get_urls_called());
    202   EXPECT_FALSE(precache_callback_.was_on_done_called());
    203   EXPECT_TRUE(url_callback_.requested_urls().empty());
    204 }
    205 
    206 TEST_F(PrecacheManagerTest, StartAndCancelPrecachingAfterURLsReceived) {
    207   EXPECT_FALSE(precache_manager_.IsPrecaching());
    208 
    209   FakeURLListProvider url_list_provider(
    210       std::list<GURL>(1, GURL("http://starting-url.com")), true);
    211 
    212   precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
    213                                     &url_list_provider);
    214 
    215   // Since the |url_list_provider| ran the callback immediately, Start() has
    216   // been called on the PrecacheFetcher, and the precache config settings have
    217   // been requested. The response has not yet been received though, so
    218   // precaching is still in progress.
    219   EXPECT_TRUE(precache_manager_.IsPrecaching());
    220 
    221   precache_manager_.CancelPrecaching();
    222   EXPECT_FALSE(precache_manager_.IsPrecaching());
    223 
    224   base::MessageLoop::current()->RunUntilIdle();
    225   EXPECT_FALSE(precache_manager_.IsPrecaching());
    226   EXPECT_TRUE(url_list_provider.was_get_urls_called());
    227   EXPECT_FALSE(precache_callback_.was_on_done_called());
    228 
    229   // Even though the response for the precache config settings should not have
    230   // been received, the request should still have been made.
    231   std::multiset<GURL> expected_requested_urls;
    232   expected_requested_urls.insert(GURL(kConfigURL));
    233   EXPECT_EQ(expected_requested_urls, url_callback_.requested_urls());
    234 }
    235 
    236 TEST_F(PrecacheManagerTest, RecordStatsForFetchWithIrrelevantFetches) {
    237   HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
    238 
    239   // Fetches with size 0 should be ignored.
    240   precache_manager_.RecordStatsForFetch(GURL("http://url.com"), base::Time(), 0,
    241                                         false);
    242   base::MessageLoop::current()->RunUntilIdle();
    243   EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
    244 
    245   // Fetches for URLs with schemes other than HTTP or HTTPS should be ignored.
    246   precache_manager_.RecordStatsForFetch(GURL("ftp://ftp.com"), base::Time(),
    247                                         1000, false);
    248   base::MessageLoop::current()->RunUntilIdle();
    249   EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
    250 
    251   // Fetches for empty URLs should be ignored.
    252   precache_manager_.RecordStatsForFetch(GURL(), base::Time(), 1000, false);
    253   base::MessageLoop::current()->RunUntilIdle();
    254   EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
    255 }
    256 
    257 TEST_F(PrecacheManagerTest, RecordStatsForFetchDuringPrecaching) {
    258   HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
    259 
    260   FakeURLListProvider url_list_provider(std::list<GURL>(), false);
    261   precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
    262                                     &url_list_provider);
    263 
    264   EXPECT_TRUE(precache_manager_.IsPrecaching());
    265   precache_manager_.RecordStatsForFetch(GURL("http://url.com"), base::Time(),
    266                                         1000, false);
    267 
    268   precache_manager_.CancelPrecaching();
    269 
    270   base::MessageLoop::current()->RunUntilIdle();
    271   expected_histogram_count_map["Precache.DownloadedPrecacheMotivated"]++;
    272   EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
    273 }
    274 
    275 TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTP) {
    276   HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
    277 
    278   precache_manager_.RecordStatsForFetch(GURL("http://http-url.com"),
    279                                         base::Time(), 1000, false);
    280   base::MessageLoop::current()->RunUntilIdle();
    281 
    282   expected_histogram_count_map["Precache.DownloadedNonPrecache"]++;
    283   EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
    284 }
    285 
    286 TEST_F(PrecacheManagerTest, RecordStatsForFetchHTTPS) {
    287   HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
    288 
    289   precache_manager_.RecordStatsForFetch(GURL("https://https-url.com"),
    290                                         base::Time(), 1000, false);
    291   base::MessageLoop::current()->RunUntilIdle();
    292 
    293   expected_histogram_count_map["Precache.DownloadedNonPrecache"]++;
    294   EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
    295 }
    296 
    297 TEST_F(PrecacheManagerTest, DeleteExpiredPrecacheHistory) {
    298   // This test has to use Time::Now() because StartPrecaching uses Time::Now().
    299   const base::Time kCurrentTime = base::Time::Now();
    300   HistogramCountMap expected_histogram_count_map = GetHistogramCountMap();
    301 
    302   FakeURLListProvider url_list_provider(std::list<GURL>(), false);
    303   precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
    304                                     &url_list_provider);
    305   EXPECT_TRUE(precache_manager_.IsPrecaching());
    306 
    307   // Precache a bunch of URLs, with different fetch times.
    308   precache_manager_.RecordStatsForFetch(
    309       GURL("http://old-fetch.com"),
    310       kCurrentTime - base::TimeDelta::FromDays(61), 1000, false);
    311   precache_manager_.RecordStatsForFetch(
    312       GURL("http://recent-fetch.com"),
    313       kCurrentTime - base::TimeDelta::FromDays(59), 1000, false);
    314   precache_manager_.RecordStatsForFetch(
    315       GURL("http://yesterday-fetch.com"),
    316       kCurrentTime - base::TimeDelta::FromDays(1), 1000, false);
    317   expected_histogram_count_map["Precache.DownloadedPrecacheMotivated"] += 3;
    318 
    319   precache_manager_.CancelPrecaching();
    320   base::MessageLoop::current()->RunUntilIdle();
    321   EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
    322 
    323   // The expired precache will be deleted during precaching this time.
    324   precache_manager_.StartPrecaching(precache_callback_.GetCallback(),
    325                                     &url_list_provider);
    326   EXPECT_TRUE(precache_manager_.IsPrecaching());
    327 
    328   precache_manager_.CancelPrecaching();
    329   base::MessageLoop::current()->RunUntilIdle();
    330   EXPECT_FALSE(precache_manager_.IsPrecaching());
    331 
    332   // A fetch for the same URL as the expired precache was served from the cache,
    333   // but it isn't reported as saved bytes because it had expired in the precache
    334   // history.
    335   precache_manager_.RecordStatsForFetch(
    336       GURL("http://old-fetch.com"),
    337       kCurrentTime, 1000, true);
    338 
    339   base::MessageLoop::current()->RunUntilIdle();
    340   EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
    341 
    342   // The other precaches should not have expired, so the following fetches from
    343   // the cache should count as saved bytes.
    344   precache_manager_.RecordStatsForFetch(
    345       GURL("http://recent-fetch.com"),
    346       kCurrentTime, 1000, true);
    347   precache_manager_.RecordStatsForFetch(
    348       GURL("http://yesterday-fetch.com"),
    349       kCurrentTime, 1000, true);
    350   expected_histogram_count_map["Precache.Saved"] += 2;
    351 
    352   base::MessageLoop::current()->RunUntilIdle();
    353   EXPECT_EQ(expected_histogram_count_map, GetHistogramCountMap());
    354 }
    355 
    356 }  // namespace
    357 
    358 }  // namespace precache
    359