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