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.
5 #include "components/precache/content/precache_manager.h"
11 #include "base/basictypes.h"
12 #include "base/bind.h"
13 #include "base/callback.h"
14 #include "base/command_line.h"
15 #include "base/compiler_specific.h"
16 #include "base/location.h"
17 #include "base/single_thread_task_runner.h"
18 #include "base/test/histogram_tester.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "components/history/core/browser/history_constants.h"
21 #include "components/history/core/browser/history_service.h"
22 #include "components/history/core/browser/history_types.h"
23 #include "components/precache/core/precache_switches.h"
24 #include "content/public/test/test_browser_context.h"
25 #include "content/public/test/test_browser_thread_bundle.h"
26 #include "net/http/http_status_code.h"
27 #include "net/url_request/test_url_fetcher_factory.h"
28 #include "net/url_request/url_request_status.h"
29 #include "net/url_request/url_request_test_util.h"
30 #include "testing/gmock/include/gmock/gmock.h"
31 #include "testing/gtest/include/gtest/gtest.h"
39 using ::testing::ContainerEq
;
40 using ::testing::ElementsAre
;
41 using ::testing::Invoke
;
42 using ::testing::IsEmpty
;
43 using ::testing::Pair
;
44 using ::testing::SaveArg
;
46 const char kConfigURL
[] = "http://config-url.com";
47 const char kManifestURLPrefix
[] = "http://manifest-url-prefix.com/";
48 const char kGoodManifestURL
[] =
49 "http://manifest-url-prefix.com/good-manifest.com";
51 class TestURLFetcherCallback
{
53 scoped_ptr
<net::FakeURLFetcher
> CreateURLFetcher(
54 const GURL
& url
, net::URLFetcherDelegate
* delegate
,
55 const std::string
& response_data
, net::HttpStatusCode response_code
,
56 net::URLRequestStatus::Status status
) {
57 scoped_ptr
<net::FakeURLFetcher
> fetcher(new net::FakeURLFetcher(
58 url
, delegate
, response_data
, response_code
, status
));
60 requested_urls_
.insert(url
);
61 return fetcher
.Pass();
64 const std::multiset
<GURL
>& requested_urls() const {
65 return requested_urls_
;
69 // Multiset with one entry for each URL requested.
70 std::multiset
<GURL
> requested_urls_
;
73 class MockHistoryService
: public history::HistoryService
{
75 MockHistoryService() {
76 ON_CALL(*this, HostRankIfAvailable(_
, _
))
77 .WillByDefault(Invoke(
78 [](const GURL
& url
, const base::Callback
<void(int)>& callback
) {
79 callback
.Run(history::kMaxTopHosts
);
83 MOCK_CONST_METHOD2(TopHosts
,
84 void(int num_hosts
, const TopHostsCallback
& callback
));
86 MOCK_CONST_METHOD2(HostRankIfAvailable
,
88 const base::Callback
<void(int)>& callback
));
91 ACTION_P(ReturnHosts
, starting_hosts
) {
92 arg1
.Run(starting_hosts
);
95 class TestPrecacheCompletionCallback
{
97 TestPrecacheCompletionCallback() : was_on_done_called_(false) {}
99 void OnDone(bool precaching_started
) { was_on_done_called_
= true; }
101 PrecacheManager::PrecacheCompletionCallback
GetCallback() {
102 return base::Bind(&TestPrecacheCompletionCallback::OnDone
,
103 base::Unretained(this));
106 bool was_on_done_called() const {
107 return was_on_done_called_
;
111 bool was_on_done_called_
;
114 class PrecacheManagerUnderTest
: public PrecacheManager
{
116 PrecacheManagerUnderTest(content::BrowserContext
* browser_context
,
117 const sync_driver::SyncService
* const sync_service
,
118 const history::HistoryService
* const history_service
)
119 : PrecacheManager(browser_context
, sync_service
, history_service
) {}
120 bool IsInExperimentGroup() const override
{ return true; }
121 bool IsInControlGroup() const override
{ return false; }
122 bool IsPrecachingAllowed() const override
{ return true; }
125 class PrecacheManagerTest
: public testing::Test
{
127 PrecacheManagerTest()
128 : precache_manager_(&browser_context_
,
129 nullptr /* sync_service */,
132 base::Bind(&TestURLFetcherCallback::CreateURLFetcher
,
133 base::Unretained(&url_callback_
))) {}
136 void SetUp() override
{
137 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
138 switches::kPrecacheConfigSettingsURL
, kConfigURL
);
139 base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
140 switches::kPrecacheManifestURLPrefix
, kManifestURLPrefix
);
142 // Make the fetch of the precache configuration settings fail. Precaching
143 // should still complete normally in this case.
144 factory_
.SetFakeResponse(GURL(kConfigURL
), "",
145 net::HTTP_INTERNAL_SERVER_ERROR
,
146 net::URLRequestStatus::FAILED
);
149 content::TestBrowserThreadBundle test_browser_thread_bundle_
;
150 content::TestBrowserContext browser_context_
;
151 PrecacheManagerUnderTest precache_manager_
;
152 TestURLFetcherCallback url_callback_
;
153 net::FakeURLFetcherFactory factory_
;
154 TestPrecacheCompletionCallback precache_callback_
;
155 testing::NiceMock
<MockHistoryService
> history_service_
;
156 base::HistogramTester histograms_
;
159 TEST_F(PrecacheManagerTest
, StartAndFinishPrecaching
) {
160 EXPECT_FALSE(precache_manager_
.IsPrecaching());
162 MockHistoryService::TopHostsCallback top_hosts_callback
;
163 EXPECT_CALL(history_service_
, TopHosts(NumTopHosts(), _
))
164 .WillOnce(SaveArg
<1>(&top_hosts_callback
));
166 factory_
.SetFakeResponse(GURL(kGoodManifestURL
), "", net::HTTP_OK
,
167 net::URLRequestStatus::SUCCESS
);
169 precache_manager_
.StartPrecaching(precache_callback_
.GetCallback());
171 EXPECT_TRUE(precache_manager_
.IsPrecaching());
173 top_hosts_callback
.Run(
174 history::TopHostsList(1, std::make_pair("good-manifest.com", 1)));
175 base::MessageLoop::current()->RunUntilIdle(); // For PrecacheFetcher.
176 EXPECT_FALSE(precache_manager_
.IsPrecaching());
177 EXPECT_TRUE(precache_callback_
.was_on_done_called());
179 std::multiset
<GURL
> expected_requested_urls
;
180 expected_requested_urls
.insert(GURL(kConfigURL
));
181 expected_requested_urls
.insert(GURL(kGoodManifestURL
));
182 EXPECT_EQ(expected_requested_urls
, url_callback_
.requested_urls());
185 TEST_F(PrecacheManagerTest
, StartAndCancelPrecachingBeforeURLsReceived
) {
186 EXPECT_FALSE(precache_manager_
.IsPrecaching());
188 MockHistoryService::TopHostsCallback top_hosts_callback
;
189 EXPECT_CALL(history_service_
, TopHosts(NumTopHosts(), _
))
190 .WillOnce(SaveArg
<1>(&top_hosts_callback
));
192 precache_manager_
.StartPrecaching(precache_callback_
.GetCallback());
193 EXPECT_TRUE(precache_manager_
.IsPrecaching());
195 precache_manager_
.CancelPrecaching();
196 EXPECT_FALSE(precache_manager_
.IsPrecaching());
198 top_hosts_callback
.Run(
199 history::TopHostsList(1, std::make_pair("starting-url.com", 1)));
200 base::MessageLoop::current()->RunUntilIdle(); // For PrecacheFetcher.
201 EXPECT_FALSE(precache_manager_
.IsPrecaching());
202 EXPECT_FALSE(precache_callback_
.was_on_done_called());
203 EXPECT_TRUE(url_callback_
.requested_urls().empty());
206 TEST_F(PrecacheManagerTest
, StartAndCancelPrecachingAfterURLsReceived
) {
207 EXPECT_FALSE(precache_manager_
.IsPrecaching());
209 EXPECT_CALL(history_service_
, TopHosts(NumTopHosts(), _
))
210 .WillOnce(ReturnHosts(
211 history::TopHostsList(1, std::make_pair("starting-url.com", 1))));
213 precache_manager_
.StartPrecaching(precache_callback_
.GetCallback());
215 // Since the |history_service_| 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());
221 precache_manager_
.CancelPrecaching();
222 EXPECT_FALSE(precache_manager_
.IsPrecaching());
224 base::MessageLoop::current()->RunUntilIdle(); // For PrecacheFetcher.
225 EXPECT_FALSE(precache_manager_
.IsPrecaching());
226 EXPECT_FALSE(precache_callback_
.was_on_done_called());
228 // Even though the response for the precache config settings should not have
229 // been received, the request should still have been made.
230 std::multiset
<GURL
> expected_requested_urls
;
231 expected_requested_urls
.insert(GURL(kConfigURL
));
232 EXPECT_EQ(expected_requested_urls
, url_callback_
.requested_urls());
235 TEST_F(PrecacheManagerTest
, RecordStatsForFetchWithSizeZero
) {
236 // Fetches with size 0 should be ignored.
237 precache_manager_
.RecordStatsForFetch(GURL("http://url.com"), GURL(),
238 base::TimeDelta(), base::Time(), 0,
240 base::MessageLoop::current()->RunUntilIdle();
241 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."), IsEmpty());
244 TEST_F(PrecacheManagerTest
, RecordStatsForFetchWithNonHTTP
) {
245 // Fetches for URLs with schemes other than HTTP or HTTPS should be ignored.
246 precache_manager_
.RecordStatsForFetch(GURL("ftp://ftp.com"), GURL(),
247 base::TimeDelta(), base::Time(), 1000,
249 base::MessageLoop::current()->RunUntilIdle();
250 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."), IsEmpty());
253 TEST_F(PrecacheManagerTest
, RecordStatsForFetchWithEmptyURL
) {
254 // Fetches for empty URLs should be ignored.
255 precache_manager_
.RecordStatsForFetch(GURL(), GURL(), base::TimeDelta(),
256 base::Time(), 1000, false);
257 base::MessageLoop::current()->RunUntilIdle();
258 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."), IsEmpty());
261 TEST_F(PrecacheManagerTest
, RecordStatsForFetchDuringPrecaching
) {
262 EXPECT_CALL(history_service_
, TopHosts(NumTopHosts(), _
))
263 .WillOnce(ReturnHosts(history::TopHostsList()));
265 precache_manager_
.StartPrecaching(precache_callback_
.GetCallback());
267 EXPECT_TRUE(precache_manager_
.IsPrecaching());
268 precache_manager_
.RecordStatsForFetch(GURL("http://url.com"), GURL(),
269 base::TimeDelta(), base::Time(), 1000,
272 precache_manager_
.CancelPrecaching();
274 // For PrecacheFetcher and RecordURLPrecached.
275 base::MessageLoop::current()->RunUntilIdle();
276 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."),
277 ElementsAre(Pair("Precache.DownloadedPrecacheMotivated", 1),
278 Pair("Precache.Fetch.PercentCompleted", 1),
279 Pair("Precache.Fetch.ResponseBytes.Network", 1),
280 Pair("Precache.Fetch.ResponseBytes.Total", 1),
281 Pair("Precache.Latency.Prefetch", 1)));
284 TEST_F(PrecacheManagerTest
, RecordStatsForFetchHTTP
) {
285 precache_manager_
.RecordStatsForFetch(GURL("http://http-url.com"), GURL(),
286 base::TimeDelta(), base::Time(), 1000,
288 base::MessageLoop::current()->RunUntilIdle();
290 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."),
291 ElementsAre(Pair("Precache.DownloadedNonPrecache", 1),
292 Pair("Precache.Latency.NonPrefetch", 1),
293 Pair("Precache.Latency.NonPrefetch.NonTopHosts", 1)));
296 TEST_F(PrecacheManagerTest
, RecordStatsForFetchHTTPS
) {
297 precache_manager_
.RecordStatsForFetch(GURL("https://https-url.com"), GURL(),
298 base::TimeDelta(), base::Time(), 1000,
300 base::MessageLoop::current()->RunUntilIdle();
302 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."),
303 ElementsAre(Pair("Precache.DownloadedNonPrecache", 1),
304 Pair("Precache.Latency.NonPrefetch", 1),
305 Pair("Precache.Latency.NonPrefetch.NonTopHosts", 1)));
308 TEST_F(PrecacheManagerTest
, RecordStatsForFetchInTopHosts
) {
309 EXPECT_CALL(history_service_
,
310 HostRankIfAvailable(GURL("http://referrer.com"), _
))
312 [](const GURL
& url
, const base::Callback
<void(int)>& callback
) {
315 precache_manager_
.RecordStatsForFetch(
316 GURL("http://http-url.com"), GURL("http://referrer.com"),
317 base::TimeDelta(), base::Time(), 1000, false);
318 base::MessageLoop::current()->RunUntilIdle();
320 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."),
321 ElementsAre(Pair("Precache.DownloadedNonPrecache", 1),
322 Pair("Precache.Latency.NonPrefetch", 1),
323 Pair("Precache.Latency.NonPrefetch.TopHosts", 1)));
326 TEST_F(PrecacheManagerTest
, DeleteExpiredPrecacheHistory
) {
327 // TODO(twifkak): Split this into multiple tests.
328 base::HistogramTester::CountsMap expected_histogram_count_map
;
330 // This test has to use Time::Now() because StartPrecaching uses Time::Now().
331 const base::Time kCurrentTime
= base::Time::Now();
332 EXPECT_CALL(history_service_
, TopHosts(NumTopHosts(), _
))
334 .WillRepeatedly(ReturnHosts(history::TopHostsList()));
336 precache_manager_
.StartPrecaching(precache_callback_
.GetCallback());
337 EXPECT_TRUE(precache_manager_
.IsPrecaching());
339 // Precache a bunch of URLs, with different fetch times.
340 precache_manager_
.RecordStatsForFetch(
341 GURL("http://old-fetch.com"), GURL(), base::TimeDelta(),
342 kCurrentTime
- base::TimeDelta::FromDays(61), 1000, false);
343 precache_manager_
.RecordStatsForFetch(
344 GURL("http://recent-fetch.com"), GURL(), base::TimeDelta(),
345 kCurrentTime
- base::TimeDelta::FromDays(59), 1000, false);
346 precache_manager_
.RecordStatsForFetch(
347 GURL("http://yesterday-fetch.com"), GURL(), base::TimeDelta(),
348 kCurrentTime
- base::TimeDelta::FromDays(1), 1000, false);
349 expected_histogram_count_map
["Precache.DownloadedPrecacheMotivated"] += 3;
350 expected_histogram_count_map
["Precache.Fetch.PercentCompleted"]++;
351 expected_histogram_count_map
["Precache.Fetch.ResponseBytes.Network"]++;
352 expected_histogram_count_map
["Precache.Fetch.ResponseBytes.Total"]++;
353 expected_histogram_count_map
["Precache.Latency.Prefetch"] += 3;
355 precache_manager_
.CancelPrecaching();
356 // For PrecacheFetcher and RecordURLPrecached.
357 base::MessageLoop::current()->RunUntilIdle();
358 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."),
359 ContainerEq(expected_histogram_count_map
));
361 // The expired precache will be deleted during precaching this time.
362 precache_manager_
.StartPrecaching(precache_callback_
.GetCallback());
363 EXPECT_TRUE(precache_manager_
.IsPrecaching());
364 expected_histogram_count_map
["Precache.Fetch.PercentCompleted"]++;
365 expected_histogram_count_map
["Precache.Fetch.ResponseBytes.Network"]++;
366 expected_histogram_count_map
["Precache.Fetch.ResponseBytes.Total"]++;
368 precache_manager_
.CancelPrecaching();
369 // For PrecacheFetcher and RecordURLPrecached.
370 base::MessageLoop::current()->RunUntilIdle();
371 EXPECT_FALSE(precache_manager_
.IsPrecaching());
373 // A fetch for the same URL as the expired precache was served from the cache,
374 // but it isn't reported as saved bytes because it had expired in the precache
376 precache_manager_
.RecordStatsForFetch(GURL("http://old-fetch.com"), GURL(),
377 base::TimeDelta(), kCurrentTime
, 1000,
379 expected_histogram_count_map
["Precache.Latency.NonPrefetch"]++;
380 expected_histogram_count_map
["Precache.Latency.NonPrefetch.NonTopHosts"]++;
382 base::MessageLoop::current()->RunUntilIdle();
383 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."),
384 ContainerEq(expected_histogram_count_map
));
386 // The other precaches should not have expired, so the following fetches from
387 // the cache should count as saved bytes.
388 precache_manager_
.RecordStatsForFetch(GURL("http://recent-fetch.com"), GURL(),
389 base::TimeDelta(), kCurrentTime
, 1000,
391 precache_manager_
.RecordStatsForFetch(GURL("http://yesterday-fetch.com"),
392 GURL(), base::TimeDelta(), kCurrentTime
,
394 expected_histogram_count_map
["Precache.Latency.NonPrefetch"] += 2;
395 expected_histogram_count_map
["Precache.Latency.NonPrefetch.NonTopHosts"] += 2;
396 expected_histogram_count_map
["Precache.Saved"] += 2;
398 base::MessageLoop::current()->RunUntilIdle();
399 EXPECT_THAT(histograms_
.GetTotalCountsForPrefix("Precache."),
400 ContainerEq(expected_histogram_count_map
));
405 } // namespace precache