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