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
GetHistogramTotalCount(const char * histogram_name)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
GetHistogramCountMap()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:
CreateURLFetcher(const GURL & url,net::URLFetcherDelegate * delegate,const std::string & response_data,net::HttpStatusCode response_code,net::URLRequestStatus::Status status)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
requested_urls() const76 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:
FakeURLListProvider(const std::list<GURL> & urls,bool run_immediately)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
GetURLs(const GetURLsCallback & callback)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
was_get_urls_called() const104 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:
TestPrecacheCompletionCallback()116 TestPrecacheCompletionCallback() : was_on_done_called_(false) {}
117
OnDone()118 void OnDone() {
119 was_on_done_called_ = true;
120 }
121
GetCallback()122 PrecacheManager::PrecacheCompletionCallback GetCallback() {
123 return base::Bind(&TestPrecacheCompletionCallback::OnDone,
124 base::Unretained(this));
125 }
126
was_on_done_called() const127 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:
PrecacheManagerTest()137 PrecacheManagerTest()
138 : precache_manager_(&browser_context_),
139 factory_(NULL, base::Bind(&TestURLFetcherCallback::CreateURLFetcher,
140 base::Unretained(&url_callback_))) {}
141
142 protected:
SetUp()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
TEST_F(PrecacheManagerTest,StartAndFinishPrecaching)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
TEST_F(PrecacheManagerTest,StartAndCancelPrecachingBeforeURLsReceived)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
TEST_F(PrecacheManagerTest,StartAndCancelPrecachingAfterURLsReceived)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
TEST_F(PrecacheManagerTest,RecordStatsForFetchWithIrrelevantFetches)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
TEST_F(PrecacheManagerTest,RecordStatsForFetchDuringPrecaching)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
TEST_F(PrecacheManagerTest,RecordStatsForFetchHTTP)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
TEST_F(PrecacheManagerTest,RecordStatsForFetchHTTPS)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
TEST_F(PrecacheManagerTest,DeleteExpiredPrecacheHistory)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