1 // Copyright 2018 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/optimization_guide/hint_cache.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "base/bind.h"
11 #include "base/files/scoped_temp_dir.h"
12 #include "base/macros.h"
13 #include "base/optional.h"
14 #include "base/run_loop.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/test/metrics/histogram_tester.h"
17 #include "base/test/task_environment.h"
18 #include "components/optimization_guide/optimization_guide_features.h"
19 #include "components/optimization_guide/optimization_guide_store.h"
20 #include "components/optimization_guide/proto/hint_cache.pb.h"
21 #include "components/optimization_guide/proto/hints.pb.h"
22 #include "components/optimization_guide/proto_database_provider_test_base.h"
23 #include "components/optimization_guide/store_update_data.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25 #include "url/gurl.h"
26
27 namespace optimization_guide {
28 namespace {
29
GetHostDomainOrg(int index)30 std::string GetHostDomainOrg(int index) {
31 return "host.domain" + base::NumberToString(index) + ".org";
32 }
33
34 class HintCacheTest : public ProtoDatabaseProviderTestBase {
35 public:
HintCacheTest()36 HintCacheTest() : loaded_hint_(nullptr) {}
37
~HintCacheTest()38 ~HintCacheTest() override {}
39
SetUp()40 void SetUp() override { ProtoDatabaseProviderTestBase::SetUp(); }
41
TearDown()42 void TearDown() override {
43 ProtoDatabaseProviderTestBase::TearDown();
44 DestroyHintCache();
45 }
46
47 protected:
48 // Creates and initializes the hint cache and optimization guide store and
49 // waits for the callback indicating that initialization is complete.
CreateAndInitializeHintCache(int memory_cache_size,bool purge_existing_data=false)50 void CreateAndInitializeHintCache(int memory_cache_size,
51 bool purge_existing_data = false) {
52 auto database_path = temp_dir_.GetPath();
53 auto database_task_runner = task_environment_.GetMainThreadTaskRunner();
54 hint_cache_ = std::make_unique<HintCache>(
55 std::make_unique<OptimizationGuideStore>(
56 db_provider_.get(), database_path, database_task_runner),
57 memory_cache_size);
58 is_store_initialized_ = false;
59 hint_cache_->Initialize(purge_existing_data,
60 base::BindOnce(&HintCacheTest::OnStoreInitialized,
61 base::Unretained(this)));
62 while (!is_store_initialized_) {
63 RunUntilIdle();
64 }
65 hint_cache_->SetClockForTesting(task_environment_.GetMockClock());
66 }
67
DestroyHintCache()68 void DestroyHintCache() {
69 hint_cache_.reset();
70 loaded_hint_ = nullptr;
71 is_store_initialized_ = false;
72 are_component_hints_updated_ = false;
73 on_load_hint_callback_called_ = false;
74 are_fetched_hints_updated_ = false;
75
76 RunUntilIdle();
77 }
78
hint_cache()79 HintCache* hint_cache() { return hint_cache_.get(); }
80
are_fetched_hints_updated()81 bool are_fetched_hints_updated() { return are_fetched_hints_updated_; }
82
83 // Updates the cache with |component_data| and waits for callback indicating
84 // that the update is complete.
UpdateComponentHints(std::unique_ptr<StoreUpdateData> component_data)85 void UpdateComponentHints(std::unique_ptr<StoreUpdateData> component_data) {
86 are_component_hints_updated_ = false;
87 hint_cache_->UpdateComponentHints(
88 std::move(component_data),
89 base::BindOnce(&HintCacheTest::OnUpdateComponentHints,
90 base::Unretained(this)));
91 while (!are_component_hints_updated_) {
92 RunUntilIdle();
93 }
94 }
95
UpdateFetchedHintsAndWait(std::unique_ptr<proto::GetHintsResponse> get_hints_response,base::Time stored_time,const base::flat_set<GURL> & urls_fetched)96 void UpdateFetchedHintsAndWait(
97 std::unique_ptr<proto::GetHintsResponse> get_hints_response,
98 base::Time stored_time,
99 const base::flat_set<GURL>& urls_fetched) {
100 are_fetched_hints_updated_ = false;
101 hint_cache_->UpdateFetchedHints(
102 std::move(get_hints_response), stored_time, urls_fetched,
103 base::BindOnce(&HintCacheTest::OnHintsUpdated, base::Unretained(this)));
104
105 while (!are_fetched_hints_updated_)
106 RunUntilIdle();
107 }
108
OnHintsUpdated()109 void OnHintsUpdated() { are_fetched_hints_updated_ = true; }
110
111 // Loads hint for the specified host from the cache and waits for callback
112 // indicating that loading the hint is complete.
LoadHint(const std::string & host)113 void LoadHint(const std::string& host) {
114 on_load_hint_callback_called_ = false;
115 loaded_hint_ = nullptr;
116 hint_cache_->LoadHint(host, base::BindOnce(&HintCacheTest::OnLoadHint,
117 base::Unretained(this)));
118 while (!on_load_hint_callback_called_) {
119 RunUntilIdle();
120 }
121 }
122
GetLoadedHint() const123 const proto::Hint* GetLoadedHint() const { return loaded_hint_; }
124
CreateHintForURL(const GURL url,base::Optional<int> cache_duration_in_secs=base::Optional<int> ())125 proto::Hint CreateHintForURL(
126 const GURL url,
127 base::Optional<int> cache_duration_in_secs = base::Optional<int>()) {
128 proto::Hint hint;
129 hint.set_key(url.spec());
130 hint.set_key_representation(proto::FULL_URL);
131 if (cache_duration_in_secs)
132 hint.mutable_max_cache_duration()->set_seconds(*cache_duration_in_secs);
133 proto::PageHint* page_hint = hint.add_page_hints();
134 page_hint->add_whitelisted_optimizations()->set_optimization_type(
135 optimization_guide::proto::PERFORMANCE_HINTS);
136 page_hint->set_page_pattern("whatever/*");
137
138 return hint;
139 }
140
MoveClockForwardBy(base::TimeDelta time_delta)141 void MoveClockForwardBy(base::TimeDelta time_delta) {
142 task_environment_.FastForwardBy(time_delta);
143 RunUntilIdle();
144 }
145
RunUntilIdle()146 void RunUntilIdle() {
147 task_environment_.RunUntilIdle();
148 base::RunLoop().RunUntilIdle();
149 }
150
151 private:
OnStoreInitialized()152 void OnStoreInitialized() { is_store_initialized_ = true; }
OnUpdateComponentHints()153 void OnUpdateComponentHints() { are_component_hints_updated_ = true; }
OnLoadHint(const proto::Hint * hint)154 void OnLoadHint(const proto::Hint* hint) {
155 on_load_hint_callback_called_ = true;
156 loaded_hint_ = hint;
157 }
158
159 base::test::TaskEnvironment task_environment_{
160 base::test::TaskEnvironment::TimeSource::MOCK_TIME};
161
162 std::unique_ptr<HintCache> hint_cache_;
163 const proto::Hint* loaded_hint_;
164
165 bool is_store_initialized_;
166 bool are_component_hints_updated_;
167 bool on_load_hint_callback_called_;
168 bool are_fetched_hints_updated_;
169
170 DISALLOW_COPY_AND_ASSIGN(HintCacheTest);
171 };
172
TEST_F(HintCacheTest,ComponentUpdate)173 TEST_F(HintCacheTest, ComponentUpdate) {
174 const int kMemoryCacheSize = 5;
175 CreateAndInitializeHintCache(kMemoryCacheSize);
176
177 base::Version version("2.0.0");
178 std::unique_ptr<StoreUpdateData> update_data =
179 hint_cache()->MaybeCreateUpdateDataForComponentHints(version);
180 ASSERT_TRUE(update_data);
181
182 proto::Hint hint1;
183 hint1.set_key("subdomain.domain.org");
184 hint1.set_key_representation(proto::HOST_SUFFIX);
185 proto::Hint hint2;
186 hint2.set_key("host.domain.org");
187 hint2.set_key_representation(proto::HOST_SUFFIX);
188 proto::Hint hint3;
189 hint3.set_key("otherhost.subdomain.domain.org");
190 hint3.set_key_representation(proto::HOST_SUFFIX);
191
192 update_data->MoveHintIntoUpdateData(std::move(hint1));
193 update_data->MoveHintIntoUpdateData(std::move(hint2));
194 update_data->MoveHintIntoUpdateData(std::move(hint3));
195
196 UpdateComponentHints(std::move(update_data));
197
198 // Not matched
199 EXPECT_FALSE(hint_cache()->HasHint("domain.org"));
200 EXPECT_FALSE(hint_cache()->HasHint("othersubdomain.domain.org"));
201
202 // Matched
203 EXPECT_TRUE(hint_cache()->HasHint("otherhost.subdomain.domain.org"));
204 EXPECT_TRUE(hint_cache()->HasHint("host.subdomain.domain.org"));
205 EXPECT_TRUE(hint_cache()->HasHint("subhost.host.subdomain.domain.org"));
206 }
207
TEST_F(HintCacheTest,ComponentUpdateWithSameVersionIgnored)208 TEST_F(HintCacheTest, ComponentUpdateWithSameVersionIgnored) {
209 const int kMemoryCacheSize = 5;
210 CreateAndInitializeHintCache(kMemoryCacheSize);
211
212 base::Version version("2.0.0");
213 std::unique_ptr<StoreUpdateData> update_data =
214 hint_cache()->MaybeCreateUpdateDataForComponentHints(version);
215 ASSERT_TRUE(update_data);
216
217 UpdateComponentHints(std::move(update_data));
218
219 EXPECT_FALSE(hint_cache()->MaybeCreateUpdateDataForComponentHints(version));
220 }
221
TEST_F(HintCacheTest,ComponentUpdateWithEarlierVersionIgnored)222 TEST_F(HintCacheTest, ComponentUpdateWithEarlierVersionIgnored) {
223 const int kMemoryCacheSize = 5;
224 CreateAndInitializeHintCache(kMemoryCacheSize);
225
226 base::Version version_1("1.0.0");
227 base::Version version_2("2.0.0");
228
229 std::unique_ptr<StoreUpdateData> update_data =
230 hint_cache()->MaybeCreateUpdateDataForComponentHints(version_2);
231 ASSERT_TRUE(update_data);
232
233 UpdateComponentHints(std::move(update_data));
234
235 EXPECT_FALSE(hint_cache()->MaybeCreateUpdateDataForComponentHints(version_1));
236 }
237
TEST_F(HintCacheTest,ComponentUpdateWithLaterVersionProcessed)238 TEST_F(HintCacheTest, ComponentUpdateWithLaterVersionProcessed) {
239 const int kMemoryCacheSize = 5;
240 CreateAndInitializeHintCache(kMemoryCacheSize);
241
242 base::Version version_1("1.0.0");
243 base::Version version_2("2.0.0");
244
245 std::unique_ptr<StoreUpdateData> update_data_1 =
246 hint_cache()->MaybeCreateUpdateDataForComponentHints(version_1);
247 ASSERT_TRUE(update_data_1);
248
249 proto::Hint hint1;
250 hint1.set_key("subdomain.domain.org");
251 hint1.set_key_representation(proto::HOST_SUFFIX);
252 proto::Hint hint2;
253 hint2.set_key("host.domain.org");
254 hint2.set_key_representation(proto::HOST_SUFFIX);
255 proto::Hint hint3;
256 hint3.set_key("otherhost.subdomain.domain.org");
257 hint3.set_key_representation(proto::HOST_SUFFIX);
258
259 update_data_1->MoveHintIntoUpdateData(std::move(hint1));
260 update_data_1->MoveHintIntoUpdateData(std::move(hint2));
261 update_data_1->MoveHintIntoUpdateData(std::move(hint3));
262
263 UpdateComponentHints(std::move(update_data_1));
264
265 // Not matched
266 EXPECT_FALSE(hint_cache()->HasHint("domain.org"));
267 EXPECT_FALSE(hint_cache()->HasHint("othersubdomain.domain.org"));
268
269 // Matched
270 EXPECT_TRUE(hint_cache()->HasHint("otherhost.subdomain.domain.org"));
271 EXPECT_TRUE(hint_cache()->HasHint("host.subdomain.domain.org"));
272 EXPECT_TRUE(hint_cache()->HasHint("subhost.host.subdomain.domain.org"));
273
274 std::unique_ptr<StoreUpdateData> update_data_2 =
275 hint_cache()->MaybeCreateUpdateDataForComponentHints(version_2);
276 ASSERT_TRUE(update_data_2);
277
278 proto::Hint hint4;
279 hint4.set_key("subdomain.domain2.org");
280 hint4.set_key_representation(proto::HOST_SUFFIX);
281 proto::Hint hint5;
282 hint5.set_key("host.domain2.org");
283 hint5.set_key_representation(proto::HOST_SUFFIX);
284 proto::Hint hint6;
285 hint6.set_key("otherhost.subdomain.domain2.org");
286 hint6.set_key_representation(proto::HOST_SUFFIX);
287
288 update_data_2->MoveHintIntoUpdateData(std::move(hint4));
289 update_data_2->MoveHintIntoUpdateData(std::move(hint5));
290 update_data_2->MoveHintIntoUpdateData(std::move(hint6));
291
292 UpdateComponentHints(std::move(update_data_2));
293
294 // Not matched
295 EXPECT_FALSE(hint_cache()->HasHint("otherhost.subdomain.domain.org"));
296 EXPECT_FALSE(hint_cache()->HasHint("host.subdomain.domain.org"));
297 EXPECT_FALSE(hint_cache()->HasHint("subhost.host.subdomain.domain.org"));
298 EXPECT_FALSE(hint_cache()->HasHint("domain2.org"));
299 EXPECT_FALSE(hint_cache()->HasHint("othersubdomain.domain2.org"));
300
301 // Matched
302 EXPECT_TRUE(hint_cache()->HasHint("otherhost.subdomain.domain2.org"));
303 EXPECT_TRUE(hint_cache()->HasHint("host.subdomain.domain2.org"));
304 EXPECT_TRUE(hint_cache()->HasHint("subhost.host.subdomain.domain2.org"));
305 }
306
TEST_F(HintCacheTest,ComponentHintsAvailableAfterRestart)307 TEST_F(HintCacheTest, ComponentHintsAvailableAfterRestart) {
308 for (int i = 0; i < 2; ++i) {
309 const int kMemoryCacheSize = 5;
310 CreateAndInitializeHintCache(kMemoryCacheSize,
311 false /*=purge_existing_data*/);
312
313 base::Version version("2.0.0");
314
315 std::unique_ptr<StoreUpdateData> update_data =
316 hint_cache()->MaybeCreateUpdateDataForComponentHints(version);
317 if (i == 0) {
318 ASSERT_TRUE(update_data);
319
320 proto::Hint hint1;
321 hint1.set_key("subdomain.domain.org");
322 hint1.set_key_representation(proto::HOST_SUFFIX);
323 proto::Hint hint2;
324 hint2.set_key("host.domain.org");
325 hint2.set_key_representation(proto::HOST_SUFFIX);
326 proto::Hint hint3;
327 hint3.set_key("otherhost.subdomain.domain.org");
328 hint3.set_key_representation(proto::HOST_SUFFIX);
329
330 update_data->MoveHintIntoUpdateData(std::move(hint1));
331 update_data->MoveHintIntoUpdateData(std::move(hint2));
332 update_data->MoveHintIntoUpdateData(std::move(hint3));
333
334 UpdateComponentHints(std::move(update_data));
335 } else {
336 EXPECT_FALSE(update_data);
337 }
338
339 // Not matched
340 EXPECT_FALSE(hint_cache()->HasHint("domain.org"));
341 EXPECT_FALSE(hint_cache()->HasHint("othersubdomain.domain.org"));
342
343 // Matched
344 EXPECT_TRUE(hint_cache()->HasHint("otherhost.subdomain.domain.org"));
345 EXPECT_TRUE(hint_cache()->HasHint("host.subdomain.domain.org"));
346 EXPECT_TRUE(hint_cache()->HasHint("subhost.host.subdomain.domain.org"));
347
348 DestroyHintCache();
349 }
350 }
351
TEST_F(HintCacheTest,ComponentHintsUpdatableAfterRestartWithPurge)352 TEST_F(HintCacheTest, ComponentHintsUpdatableAfterRestartWithPurge) {
353 for (int i = 0; i < 2; ++i) {
354 const int kMemoryCacheSize = 5;
355 CreateAndInitializeHintCache(kMemoryCacheSize,
356 true /*=purge_existing_data*/);
357
358 base::Version version("2.0.0");
359
360 std::unique_ptr<StoreUpdateData> update_data =
361 hint_cache()->MaybeCreateUpdateDataForComponentHints(version);
362 ASSERT_TRUE(update_data);
363
364 proto::Hint hint1;
365 hint1.set_key("subdomain.domain.org");
366 hint1.set_key_representation(proto::HOST_SUFFIX);
367 proto::Hint hint2;
368 hint2.set_key("host.domain.org");
369 hint2.set_key_representation(proto::HOST_SUFFIX);
370 proto::Hint hint3;
371 hint3.set_key("otherhost.subdomain.domain.org");
372 hint3.set_key_representation(proto::HOST_SUFFIX);
373
374 update_data->MoveHintIntoUpdateData(std::move(hint1));
375 update_data->MoveHintIntoUpdateData(std::move(hint2));
376 update_data->MoveHintIntoUpdateData(std::move(hint3));
377
378 UpdateComponentHints(std::move(update_data));
379
380 // Not matched
381 EXPECT_FALSE(hint_cache()->HasHint("domain.org"));
382 EXPECT_FALSE(hint_cache()->HasHint("othersubdomain.domain.org"));
383
384 // Matched
385 EXPECT_TRUE(hint_cache()->HasHint("otherhost.subdomain.domain.org"));
386 EXPECT_TRUE(hint_cache()->HasHint("host.subdomain.domain.org"));
387 EXPECT_TRUE(hint_cache()->HasHint("subhost.host.subdomain.domain.org"));
388
389 DestroyHintCache();
390 }
391 }
392
TEST_F(HintCacheTest,ComponentHintsNotRetainedAfterRestartWithPurge)393 TEST_F(HintCacheTest, ComponentHintsNotRetainedAfterRestartWithPurge) {
394 for (int i = 0; i < 2; ++i) {
395 const int kMemoryCacheSize = 5;
396 CreateAndInitializeHintCache(kMemoryCacheSize,
397 true /*=purge_existing_data*/);
398
399 base::Version version("2.0.0");
400
401 std::unique_ptr<StoreUpdateData> update_data =
402 hint_cache()->MaybeCreateUpdateDataForComponentHints(version);
403 if (i == 0) {
404 ASSERT_TRUE(update_data);
405
406 proto::Hint hint1;
407 hint1.set_key("subdomain.domain.org");
408 hint1.set_key_representation(proto::HOST_SUFFIX);
409 proto::Hint hint2;
410 hint2.set_key("host.domain.org");
411 hint2.set_key_representation(proto::HOST_SUFFIX);
412 proto::Hint hint3;
413 hint3.set_key("otherhost.subdomain.domain.org");
414 hint3.set_key_representation(proto::HOST_SUFFIX);
415
416 update_data->MoveHintIntoUpdateData(std::move(hint1));
417 update_data->MoveHintIntoUpdateData(std::move(hint2));
418 update_data->MoveHintIntoUpdateData(std::move(hint3));
419
420 UpdateComponentHints(std::move(update_data));
421 } else {
422 EXPECT_TRUE(update_data);
423 }
424
425 // Not matched
426 EXPECT_FALSE(hint_cache()->HasHint("domain.org"));
427 EXPECT_FALSE(hint_cache()->HasHint("othersubdomain.domain.org"));
428
429 // Maybe matched
430 bool should_match = (i == 0);
431 EXPECT_EQ(hint_cache()->HasHint("otherhost.subdomain.domain.org"),
432 should_match);
433 EXPECT_EQ(hint_cache()->HasHint("host.subdomain.domain.org"), should_match);
434 EXPECT_EQ(hint_cache()->HasHint("subhost.host.subdomain.domain.org"),
435 should_match);
436
437 DestroyHintCache();
438 }
439 }
440
TEST_F(HintCacheTest,TestMemoryCacheLeastRecentlyUsedPurge)441 TEST_F(HintCacheTest, TestMemoryCacheLeastRecentlyUsedPurge) {
442 const int kTestHintCount = 10;
443 const int kMemoryCacheSize = 5;
444 CreateAndInitializeHintCache(kMemoryCacheSize);
445
446 base::Version version("1.0.0");
447 std::unique_ptr<StoreUpdateData> update_data =
448 hint_cache()->MaybeCreateUpdateDataForComponentHints(version);
449 ASSERT_TRUE(update_data);
450
451 for (int i = 0; i < kTestHintCount; ++i) {
452 proto::Hint hint;
453 hint.set_key(GetHostDomainOrg(i));
454 hint.set_key_representation(proto::HOST_SUFFIX);
455 update_data->MoveHintIntoUpdateData(std::move(hint));
456 }
457
458 UpdateComponentHints(std::move(update_data));
459
460 for (int i = kTestHintCount - 1; i >= 0; --i) {
461 std::string host = GetHostDomainOrg(i);
462 EXPECT_TRUE(hint_cache()->HasHint(host));
463 LoadHint(host);
464 ASSERT_TRUE(GetLoadedHint());
465 EXPECT_EQ(GetLoadedHint()->key(), host);
466 }
467
468 for (int i = 0; i < kTestHintCount; ++i) {
469 std::string host = GetHostDomainOrg(i);
470 if (i < kMemoryCacheSize) {
471 ASSERT_TRUE(hint_cache()->GetHostKeyedHintIfLoaded(host));
472 EXPECT_EQ(GetHostDomainOrg(i),
473 hint_cache()->GetHostKeyedHintIfLoaded(host)->key());
474 } else {
475 EXPECT_FALSE(hint_cache()->GetHostKeyedHintIfLoaded(host));
476 }
477 EXPECT_TRUE(hint_cache()->HasHint(host));
478 }
479 }
480
TEST_F(HintCacheTest,TestHostNotInCache)481 TEST_F(HintCacheTest, TestHostNotInCache) {
482 const int kTestHintCount = 10;
483 const int kMemoryCacheSize = 5;
484 CreateAndInitializeHintCache(kMemoryCacheSize);
485
486 base::Version version("1.0.0");
487 std::unique_ptr<StoreUpdateData> update_data =
488 hint_cache()->MaybeCreateUpdateDataForComponentHints(version);
489 ASSERT_TRUE(update_data);
490
491 for (int i = 0; i < kTestHintCount; ++i) {
492 proto::Hint hint;
493 hint.set_key(GetHostDomainOrg(i));
494 hint.set_key_representation(proto::HOST_SUFFIX);
495 update_data->MoveHintIntoUpdateData(std::move(hint));
496 }
497
498 UpdateComponentHints(std::move(update_data));
499
500 EXPECT_FALSE(hint_cache()->HasHint(GetHostDomainOrg(kTestHintCount)));
501 }
502
TEST_F(HintCacheTest,TestMemoryCacheLoadCallback)503 TEST_F(HintCacheTest, TestMemoryCacheLoadCallback) {
504 const int kMemoryCacheSize = 5;
505 CreateAndInitializeHintCache(kMemoryCacheSize);
506
507 base::Version version("1.0.0");
508 std::unique_ptr<StoreUpdateData> update_data =
509 hint_cache()->MaybeCreateUpdateDataForComponentHints(version);
510 ASSERT_TRUE(update_data);
511
512 std::string hint_key = "subdomain.domain.org";
513 proto::Hint hint;
514 hint.set_key(hint_key);
515 hint.set_key_representation(proto::HOST_SUFFIX);
516 update_data->MoveHintIntoUpdateData(std::move(hint));
517
518 UpdateComponentHints(std::move(update_data));
519
520 EXPECT_FALSE(
521 hint_cache()->GetHostKeyedHintIfLoaded("host.subdomain.domain.org"));
522 LoadHint("host.subdomain.domain.org");
523 EXPECT_TRUE(
524 hint_cache()->GetHostKeyedHintIfLoaded("host.subdomain.domain.org"));
525
526 EXPECT_TRUE(GetLoadedHint());
527 EXPECT_EQ(hint_key, GetLoadedHint()->key());
528 }
529
TEST_F(HintCacheTest,StoreValidFetchedHints)530 TEST_F(HintCacheTest, StoreValidFetchedHints) {
531 const int kMemoryCacheSize = 5;
532 CreateAndInitializeHintCache(kMemoryCacheSize);
533
534 // Default update time for empty optimization guide store is base::Time().
535 EXPECT_EQ(hint_cache()->GetFetchedHintsUpdateTime(), base::Time());
536
537 std::unique_ptr<proto::GetHintsResponse> get_hints_response =
538 std::make_unique<proto::GetHintsResponse>();
539
540 proto::Hint* hint = get_hints_response->add_hints();
541 hint->set_key_representation(proto::HOST_SUFFIX);
542 hint->set_key("host.domain.org");
543 proto::PageHint* page_hint = hint->add_page_hints();
544 page_hint->set_page_pattern("page pattern");
545
546 base::Time stored_time = base::Time().Now();
547 UpdateFetchedHintsAndWait(std::move(get_hints_response), stored_time, {});
548 EXPECT_TRUE(are_fetched_hints_updated());
549
550 // Next update time for hints should be updated.
551 EXPECT_EQ(hint_cache()->GetFetchedHintsUpdateTime(), stored_time);
552 }
553
TEST_F(HintCacheTest,ParseEmptyFetchedHints)554 TEST_F(HintCacheTest, ParseEmptyFetchedHints) {
555 const int kMemoryCacheSize = 5;
556 CreateAndInitializeHintCache(kMemoryCacheSize);
557
558 base::Time stored_time = base::Time().Now() + base::TimeDelta().FromDays(1);
559 std::unique_ptr<proto::GetHintsResponse> get_hints_response =
560 std::make_unique<proto::GetHintsResponse>();
561
562 UpdateFetchedHintsAndWait(std::move(get_hints_response), stored_time, {});
563 // Empty Fetched Hints causes the metadata entry to be updated.
564 EXPECT_TRUE(are_fetched_hints_updated());
565 EXPECT_EQ(hint_cache()->GetFetchedHintsUpdateTime(), stored_time);
566 }
567
TEST_F(HintCacheTest,StoreValidFetchedHintsWithServerProvidedExpiryTime)568 TEST_F(HintCacheTest, StoreValidFetchedHintsWithServerProvidedExpiryTime) {
569 base::HistogramTester histogram_tester;
570 const int kMemoryCacheSize = 5;
571 const int kFetchedHintExpirationSecs = 60;
572 CreateAndInitializeHintCache(kMemoryCacheSize);
573
574 // Default update time for empty optimization guide store is base::Time().
575 EXPECT_EQ(hint_cache()->GetFetchedHintsUpdateTime(), base::Time());
576
577 std::unique_ptr<proto::GetHintsResponse> get_hints_response =
578 std::make_unique<proto::GetHintsResponse>();
579
580 // Set server-provided expiration time.
581 proto::Hint* hint = get_hints_response->add_hints();
582 hint->set_key_representation(proto::HOST_SUFFIX);
583 hint->set_key("host.domain.org");
584 hint->mutable_max_cache_duration()->set_seconds(kFetchedHintExpirationSecs);
585 proto::PageHint* page_hint = hint->add_page_hints();
586 page_hint->set_page_pattern("page pattern");
587
588 base::Time stored_time = base::Time().Now();
589 GURL navigation_url("https://foo.com");
590 UpdateFetchedHintsAndWait(std::move(get_hints_response), stored_time,
591 {navigation_url});
592 EXPECT_TRUE(are_fetched_hints_updated());
593
594 // Next update time for hints should be updated.
595 EXPECT_EQ(hint_cache()->GetFetchedHintsUpdateTime(), stored_time);
596
597 LoadHint("host.domain.org");
598 // HISTOGRAM TEST!
599 histogram_tester.ExpectTimeBucketCount(
600 "OptimizationGuide.HintCache.FetchedHint.TimeToExpiration",
601 base::TimeDelta::FromSeconds(kFetchedHintExpirationSecs), 1);
602 EXPECT_FALSE(hint_cache()->GetURLKeyedHint(navigation_url));
603 }
604
TEST_F(HintCacheTest,StoreValidFetchedHintsWithDefaultExpiryTime)605 TEST_F(HintCacheTest, StoreValidFetchedHintsWithDefaultExpiryTime) {
606 base::HistogramTester histogram_tester;
607 const int kMemoryCacheSize = 5;
608 CreateAndInitializeHintCache(kMemoryCacheSize);
609
610 // Default update time for empty optimization guide store is base::Time().
611 EXPECT_EQ(hint_cache()->GetFetchedHintsUpdateTime(), base::Time());
612
613 std::unique_ptr<proto::GetHintsResponse> get_hints_response =
614 std::make_unique<proto::GetHintsResponse>();
615
616 proto::Hint* hint = get_hints_response->add_hints();
617 hint->set_key_representation(proto::HOST_SUFFIX);
618 hint->set_key("host.domain.org");
619 proto::PageHint* page_hint = hint->add_page_hints();
620 page_hint->set_page_pattern("page pattern");
621
622 base::Time stored_time = base::Time().Now();
623 UpdateFetchedHintsAndWait(std::move(get_hints_response), stored_time, {});
624 EXPECT_TRUE(are_fetched_hints_updated());
625
626 // Next update time for hints should be updated.
627 EXPECT_EQ(hint_cache()->GetFetchedHintsUpdateTime(), stored_time);
628
629 LoadHint("host.domain.org");
630 histogram_tester.ExpectTimeBucketCount(
631 "OptimizationGuide.HintCache.FetchedHint.TimeToExpiration",
632 optimization_guide::features::StoredFetchedHintsFreshnessDuration(), 1);
633 }
634
TEST_F(HintCacheTest,CacheValidURLKeyedHint)635 TEST_F(HintCacheTest, CacheValidURLKeyedHint) {
636 const int kMemoryCacheSize = 5;
637 CreateAndInitializeHintCache(kMemoryCacheSize);
638
639 std::unique_ptr<StoreUpdateData> update_data =
640 hint_cache()->CreateUpdateDataForFetchedHints(base::Time());
641 ASSERT_TRUE(update_data);
642
643 GURL url("https://whatever.com/r/werd");
644
645 google::protobuf::RepeatedPtrField<proto::Hint> hints;
646 *(hints.Add()) = CreateHintForURL(url);
647
648 // Only URL-keyed hint included so there are no hints to store within the
649 // update data.
650 EXPECT_FALSE(hint_cache()->ProcessAndCacheHints(&hints, update_data.get()));
651
652 EXPECT_TRUE(hint_cache()->GetURLKeyedHint(url));
653 }
654
TEST_F(HintCacheTest,URLKeyedHintExpired)655 TEST_F(HintCacheTest, URLKeyedHintExpired) {
656 const int kMemoryCacheSize = 5;
657 CreateAndInitializeHintCache(kMemoryCacheSize);
658
659 std::unique_ptr<StoreUpdateData> update_data =
660 hint_cache()->CreateUpdateDataForFetchedHints(base::Time());
661 ASSERT_TRUE(update_data);
662
663 GURL url("https://whatever.com/r/werd");
664 int cache_duration_in_secs = 60;
665
666 google::protobuf::RepeatedPtrField<proto::Hint> hints;
667 *(hints.Add()) = CreateHintForURL(url, cache_duration_in_secs);
668
669 // Only URL-keyed hint included so there are no hints to store within the
670 // update data.
671 EXPECT_FALSE(hint_cache()->ProcessAndCacheHints(&hints, update_data.get()));
672
673 EXPECT_TRUE(hint_cache()->GetURLKeyedHint(url));
674
675 MoveClockForwardBy(base::TimeDelta().FromSeconds(cache_duration_in_secs + 1));
676 EXPECT_FALSE(hint_cache()->GetURLKeyedHint(url));
677 }
678
TEST_F(HintCacheTest,PurgeExpiredFetchedHints)679 TEST_F(HintCacheTest, PurgeExpiredFetchedHints) {
680 const int kMemoryCacheSize = 5;
681 CreateAndInitializeHintCache(kMemoryCacheSize);
682
683 std::unique_ptr<StoreUpdateData> update_data =
684 hint_cache()->CreateUpdateDataForFetchedHints(base::Time());
685 ASSERT_TRUE(update_data);
686
687 int cache_duration_in_secs = 60;
688
689 std::unique_ptr<proto::GetHintsResponse> get_hints_response =
690 std::make_unique<proto::GetHintsResponse>();
691
692 std::string host = "shouldpurge.com";
693 proto::Hint* hint1 = get_hints_response->add_hints();
694 hint1->set_key_representation(proto::HOST_SUFFIX);
695 hint1->set_key(host);
696 hint1->mutable_max_cache_duration()->set_seconds(cache_duration_in_secs);
697 proto::PageHint* page_hint1 = hint1->add_page_hints();
698 page_hint1->set_page_pattern("page pattern");
699 std::string host2 = "notpurged.com";
700 proto::Hint* hint2 = get_hints_response->add_hints();
701 hint2->set_key_representation(proto::HOST_SUFFIX);
702 hint2->set_key(host2);
703 hint2->mutable_max_cache_duration()->set_seconds(cache_duration_in_secs * 2);
704 proto::PageHint* page_hint2 = hint2->add_page_hints();
705 page_hint2->set_page_pattern("page pattern");
706
707 base::Time stored_time = base::Time().Now();
708 UpdateFetchedHintsAndWait(std::move(get_hints_response), stored_time, {});
709 EXPECT_TRUE(are_fetched_hints_updated());
710 EXPECT_TRUE(hint_cache()->HasHint("shouldpurge.com"));
711 EXPECT_TRUE(hint_cache()->HasHint("notpurged.com"));
712
713 MoveClockForwardBy(base::TimeDelta().FromSeconds(cache_duration_in_secs + 1));
714
715 hint_cache()->PurgeExpiredFetchedHints();
716 RunUntilIdle();
717
718 EXPECT_FALSE(hint_cache()->HasHint("shouldpurge.com"));
719 EXPECT_TRUE(hint_cache()->HasHint("notpurged.com"));
720 }
721
TEST_F(HintCacheTest,ClearFetchedHints)722 TEST_F(HintCacheTest, ClearFetchedHints) {
723 const int kMemoryCacheSize = 5;
724 CreateAndInitializeHintCache(kMemoryCacheSize);
725
726 std::unique_ptr<StoreUpdateData> update_data =
727 hint_cache()->CreateUpdateDataForFetchedHints(base::Time());
728 ASSERT_TRUE(update_data);
729
730 GURL url("https://whatever.com/r/werd");
731 int cache_duration_in_secs = 60;
732
733 google::protobuf::RepeatedPtrField<proto::Hint> hints;
734 *(hints.Add()) = CreateHintForURL(url, cache_duration_in_secs);
735
736 std::unique_ptr<proto::GetHintsResponse> get_hints_response =
737 std::make_unique<proto::GetHintsResponse>();
738
739 std::string host = "host.com";
740 proto::Hint* hint = get_hints_response->add_hints();
741 hint->set_key_representation(proto::HOST_SUFFIX);
742 hint->set_key(host);
743 proto::PageHint* page_hint = hint->add_page_hints();
744 page_hint->set_page_pattern("page pattern");
745
746 base::Time stored_time = base::Time().Now();
747 UpdateFetchedHintsAndWait(std::move(get_hints_response), stored_time, {});
748 EXPECT_TRUE(are_fetched_hints_updated());
749 LoadHint(host);
750
751 // Only URL-keyed hint included so there are no hints to store within the
752 // update data.
753 EXPECT_FALSE(hint_cache()->ProcessAndCacheHints(&hints, update_data.get()));
754
755 EXPECT_TRUE(hint_cache()->GetURLKeyedHint(url));
756 EXPECT_TRUE(hint_cache()->GetHostKeyedHintIfLoaded(host));
757
758 hint_cache()->ClearFetchedHints();
759 EXPECT_FALSE(hint_cache()->GetURLKeyedHint(url));
760 EXPECT_FALSE(hint_cache()->GetHostKeyedHintIfLoaded(host));
761 }
762
TEST_F(HintCacheTest,UnsupportedURLsForURLKeyedHints)763 TEST_F(HintCacheTest, UnsupportedURLsForURLKeyedHints) {
764 const int kMemoryCacheSize = 5;
765 CreateAndInitializeHintCache(kMemoryCacheSize);
766
767 std::unique_ptr<StoreUpdateData> update_data =
768 hint_cache()->CreateUpdateDataForFetchedHints(base::Time());
769 ASSERT_TRUE(update_data);
770
771 GURL https_url("https://whatever.com/r/werd");
772 GURL http_url("http://werd.com/werd/");
773 GURL file_url("file://dog.png");
774 GURL chrome_url("chrome://dog.png");
775 GURL auth_url("https://username:password@www.example.com/");
776
777 google::protobuf::RepeatedPtrField<proto::Hint> hints;
778 *(hints.Add()) = CreateHintForURL(https_url);
779 *(hints.Add()) = CreateHintForURL(http_url);
780 *(hints.Add()) = CreateHintForURL(file_url);
781 *(hints.Add()) = CreateHintForURL(chrome_url);
782 *(hints.Add()) = CreateHintForURL(auth_url);
783
784 // Only URL-keyed hint included so there are no hints to store within the
785 // update data.
786 EXPECT_FALSE(hint_cache()->ProcessAndCacheHints(&hints, update_data.get()));
787
788 EXPECT_TRUE(hint_cache()->GetURLKeyedHint(https_url));
789 EXPECT_TRUE(hint_cache()->GetURLKeyedHint(http_url));
790 EXPECT_FALSE(hint_cache()->GetURLKeyedHint(file_url));
791 EXPECT_FALSE(hint_cache()->GetURLKeyedHint(chrome_url));
792 EXPECT_FALSE(hint_cache()->GetURLKeyedHint(auth_url));
793 }
794
TEST_F(HintCacheTest,URLsWithNoURLKeyedHints)795 TEST_F(HintCacheTest, URLsWithNoURLKeyedHints) {
796 const int kMemoryCacheSize = 5;
797 CreateAndInitializeHintCache(kMemoryCacheSize);
798
799 std::unique_ptr<StoreUpdateData> update_data =
800 hint_cache()->CreateUpdateDataForFetchedHints(base::Time());
801 ASSERT_TRUE(update_data);
802
803 GURL https_url_without_hint("https://whatever.com/r/nohint");
804 GURL https_url_with_hint("https://whatever.com/r/hint");
805 GURL https_url_unseen("https://unseen.com/new");
806 GURL file_url("file://dog.png");
807 GURL chrome_url("chrome://dog.png");
808 GURL auth_url("https://username:password@www.example.com/");
809
810 google::protobuf::RepeatedPtrField<proto::Hint> hints;
811 *(hints.Add()) = CreateHintForURL(https_url_with_hint);
812
813 // Only URL-keyed hint included so there are no hints to store within the
814 // update data.
815 EXPECT_FALSE(hint_cache()->ProcessAndCacheHints(&hints, update_data.get()));
816
817 // Add the url without hint to the url-keyed cache via UpdateFetchedHints.
818 std::unique_ptr<proto::GetHintsResponse> get_hints_response =
819 std::make_unique<proto::GetHintsResponse>();
820
821 std::string host = "host.com";
822 proto::Hint* hint = get_hints_response->add_hints();
823 hint->set_key_representation(proto::HOST_SUFFIX);
824 hint->set_key(host);
825 proto::PageHint* page_hint = hint->add_page_hints();
826 page_hint->set_page_pattern("page pattern");
827
828 base::Time stored_time = base::Time().Now();
829 UpdateFetchedHintsAndWait(std::move(get_hints_response), stored_time,
830 {https_url_without_hint});
831
832 EXPECT_TRUE(hint_cache()->HasURLKeyedEntryForURL(https_url_with_hint));
833 EXPECT_TRUE(hint_cache()->HasURLKeyedEntryForURL(https_url_with_hint));
834 EXPECT_FALSE(hint_cache()->HasURLKeyedEntryForURL(file_url));
835 EXPECT_FALSE(hint_cache()->HasURLKeyedEntryForURL(chrome_url));
836 EXPECT_FALSE(hint_cache()->HasURLKeyedEntryForURL(auth_url));
837 EXPECT_FALSE(hint_cache()->HasURLKeyedEntryForURL(https_url_unseen));
838 }
839
TEST_F(HintCacheTest,ProcessHintsNoUpdateData)840 TEST_F(HintCacheTest, ProcessHintsNoUpdateData) {
841 const int kMemoryCacheSize = 5;
842 CreateAndInitializeHintCache(kMemoryCacheSize);
843
844 proto::Hint hint;
845 hint.set_key("whatever.com");
846 hint.set_key_representation(proto::HOST_SUFFIX);
847 proto::PageHint* page_hint = hint.add_page_hints();
848 page_hint->set_page_pattern("foo.org/*/one/");
849
850 google::protobuf::RepeatedPtrField<proto::Hint> hints;
851 *(hints.Add()) = hint;
852
853 EXPECT_FALSE(hint_cache()->ProcessAndCacheHints(&hints, nullptr));
854 }
855
TEST_F(HintCacheTest,ProcessHintsWithNoPageHintsAndUpdateData)856 TEST_F(HintCacheTest, ProcessHintsWithNoPageHintsAndUpdateData) {
857 const int kMemoryCacheSize = 5;
858 CreateAndInitializeHintCache(kMemoryCacheSize);
859
860 proto::Hint hint;
861 hint.set_key("whatever.com");
862 hint.set_key_representation(proto::HOST_SUFFIX);
863
864 google::protobuf::RepeatedPtrField<proto::Hint> hints;
865 *(hints.Add()) = hint;
866
867 std::unique_ptr<StoreUpdateData> update_data =
868 StoreUpdateData::CreateComponentStoreUpdateData(base::Version("1.0.0"));
869 EXPECT_FALSE(hint_cache()->ProcessAndCacheHints(&hints, update_data.get()));
870 // Verify there is 1 store entries: 1 for the metadata entry.
871 EXPECT_EQ(1ul, update_data->TakeUpdateEntries()->size());
872 }
873
TEST_F(HintCacheTest,ProcessHintsWithPageHintsAndUpdateData)874 TEST_F(HintCacheTest, ProcessHintsWithPageHintsAndUpdateData) {
875 const int kMemoryCacheSize = 5;
876 CreateAndInitializeHintCache(kMemoryCacheSize);
877
878 google::protobuf::RepeatedPtrField<proto::Hint> hints;
879
880 proto::Hint hint;
881 hint.set_key("foo.org");
882 hint.set_key_representation(proto::HOST_SUFFIX);
883 proto::PageHint* page_hint = hint.add_page_hints();
884 page_hint->set_page_pattern("foo.org/*/one/");
885 *(hints.Add()) = hint;
886
887 proto::Hint no_page_hints_hint;
888 no_page_hints_hint.set_key("whatever.com");
889 no_page_hints_hint.set_key_representation(proto::HOST_SUFFIX);
890 *(hints.Add()) = no_page_hints_hint;
891
892 std::unique_ptr<StoreUpdateData> update_data =
893 StoreUpdateData::CreateComponentStoreUpdateData(base::Version("1.0.0"));
894 EXPECT_TRUE(hint_cache()->ProcessAndCacheHints(&hints, update_data.get()));
895 // Verify there are 2 store entries: 1 for the metadata entry plus
896 // the 1 added hint entries.
897 EXPECT_EQ(2ul, update_data->TakeUpdateEntries()->size());
898 }
899
900 } // namespace
901
902 } // namespace optimization_guide
903