xref: /aosp_15_r20/external/cronet/base/containers/lru_cache_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2011 The Chromium Authors
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 "base/containers/lru_cache.h"
6 
7 #include <cstddef>
8 #include <memory>
9 #include <string>
10 
11 #include "base/memory/ref_counted.h"
12 #include "base/memory/scoped_refptr.h"
13 #include "base/tracing_buildflags.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 
16 #if BUILDFLAG(ENABLE_BASE_TRACING)
17 #include "base/trace_event/memory_usage_estimator.h"  // no-presubmit-check
18 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
19 
20 namespace base {
21 
22 namespace {
23 
24 int cached_item_live_count = 0;
25 
26 struct CachedItem {
CachedItembase::__anon287a6ea90111::CachedItem27   CachedItem() : value(0) { cached_item_live_count++; }
28 
CachedItembase::__anon287a6ea90111::CachedItem29   explicit CachedItem(int new_value) : value(new_value) {
30     cached_item_live_count++;
31   }
32 
CachedItembase::__anon287a6ea90111::CachedItem33   CachedItem(const CachedItem& other) : value(other.value) {
34     cached_item_live_count++;
35   }
36 
~CachedItembase::__anon287a6ea90111::CachedItem37   ~CachedItem() { cached_item_live_count--; }
38 
39   int value;
40 };
41 
42 }  // namespace
43 
44 template <typename LRUCacheTemplate>
45 class LRUCacheTest : public testing::Test {};
46 
47 struct LRUCacheTemplate {
48   template <class Key, class Value, class KeyCompare = std::less<Key>>
49   using Type = base::LRUCache<Key, Value, KeyCompare>;
50 };
51 
52 struct HashingLRUCacheTemplate {
53   template <class Key,
54             class Value,
55             class KeyHash = std::hash<Key>,
56             class KeyEqual = std::equal_to<Key>>
57   using Type = base::HashingLRUCache<Key, Value, KeyHash, KeyEqual>;
58 };
59 
60 using LRUCacheTemplates =
61     testing::Types<LRUCacheTemplate, HashingLRUCacheTemplate>;
62 TYPED_TEST_SUITE(LRUCacheTest, LRUCacheTemplates);
63 
64 template <typename LRUCacheSetTemplate>
65 class LRUCacheSetTest : public testing::Test {};
66 
67 struct LRUCacheSetTemplate {
68   template <class Value, class Compare = std::less<Value>>
69   using Type = base::LRUCacheSet<Value, Compare>;
70 };
71 
72 struct HashingLRUCacheSetTemplate {
73   template <class Value,
74             class Hash = std::hash<Value>,
75             class Equal = std::equal_to<Value>>
76   using Type = base::HashingLRUCacheSet<Value, Hash, Equal>;
77 };
78 
79 using LRUCacheSetTemplates =
80     testing::Types<LRUCacheSetTemplate, HashingLRUCacheSetTemplate>;
81 TYPED_TEST_SUITE(LRUCacheSetTest, LRUCacheSetTemplates);
82 
TYPED_TEST(LRUCacheTest,Basic)83 TYPED_TEST(LRUCacheTest, Basic) {
84   typedef typename TypeParam::template Type<int, CachedItem> Cache;
85   Cache cache(Cache::NO_AUTO_EVICT);
86 
87   // Check failure conditions
88   {
89     CachedItem test_item;
90     EXPECT_TRUE(cache.Get(0) == cache.end());
91     EXPECT_TRUE(cache.Peek(0) == cache.end());
92   }
93 
94   static const int kItem1Key = 5;
95   CachedItem item1(10);
96   auto inserted_item = cache.Put(kItem1Key, item1);
97   EXPECT_EQ(1U, cache.size());
98 
99   // Check that item1 was properly inserted.
100   {
101     auto found = cache.Get(kItem1Key);
102     EXPECT_TRUE(inserted_item == cache.begin());
103     EXPECT_TRUE(found != cache.end());
104 
105     found = cache.Peek(kItem1Key);
106     EXPECT_TRUE(found != cache.end());
107 
108     EXPECT_EQ(kItem1Key, found->first);
109     EXPECT_EQ(item1.value, found->second.value);
110   }
111 
112   static const int kItem2Key = 7;
113   CachedItem item2(12);
114   cache.Put(kItem2Key, item2);
115   EXPECT_EQ(2U, cache.size());
116 
117   // Check that item1 is the oldest since item2 was added afterwards.
118   {
119     auto oldest = cache.rbegin();
120     ASSERT_TRUE(oldest != cache.rend());
121     EXPECT_EQ(kItem1Key, oldest->first);
122     EXPECT_EQ(item1.value, oldest->second.value);
123   }
124 
125   // Check that item1 is still accessible by key.
126   {
127     auto test_item = cache.Get(kItem1Key);
128     ASSERT_TRUE(test_item != cache.end());
129     EXPECT_EQ(kItem1Key, test_item->first);
130     EXPECT_EQ(item1.value, test_item->second.value);
131   }
132 
133   // Check that retrieving item1 pushed item2 to oldest.
134   {
135     auto oldest = cache.rbegin();
136     ASSERT_TRUE(oldest != cache.rend());
137     EXPECT_EQ(kItem2Key, oldest->first);
138     EXPECT_EQ(item2.value, oldest->second.value);
139   }
140 
141   // Remove the oldest item and check that item1 is now the only member.
142   {
143     auto next = cache.Erase(cache.rbegin());
144 
145     EXPECT_EQ(1U, cache.size());
146 
147     EXPECT_TRUE(next == cache.rbegin());
148     EXPECT_EQ(kItem1Key, next->first);
149     EXPECT_EQ(item1.value, next->second.value);
150 
151     cache.Erase(cache.begin());
152     EXPECT_EQ(0U, cache.size());
153   }
154 
155   // Check that Clear() works properly.
156   cache.Put(kItem1Key, item1);
157   cache.Put(kItem2Key, item2);
158   EXPECT_EQ(2U, cache.size());
159   cache.Clear();
160   EXPECT_EQ(0U, cache.size());
161 }
162 
TYPED_TEST(LRUCacheTest,GetVsPeek)163 TYPED_TEST(LRUCacheTest, GetVsPeek) {
164   typedef typename TypeParam::template Type<int, CachedItem> Cache;
165   Cache cache(Cache::NO_AUTO_EVICT);
166 
167   static const int kItem1Key = 1;
168   CachedItem item1(10);
169   cache.Put(kItem1Key, item1);
170 
171   static const int kItem2Key = 2;
172   CachedItem item2(20);
173   cache.Put(kItem2Key, item2);
174 
175   // This should do nothing since the size is bigger than the number of items.
176   cache.ShrinkToSize(100);
177 
178   // Check that item1 starts out as oldest
179   {
180     auto iter = cache.rbegin();
181     ASSERT_TRUE(iter != cache.rend());
182     EXPECT_EQ(kItem1Key, iter->first);
183     EXPECT_EQ(item1.value, iter->second.value);
184   }
185 
186   // Check that Peek doesn't change ordering
187   {
188     auto peekiter = cache.Peek(kItem1Key);
189     ASSERT_TRUE(peekiter != cache.end());
190 
191     auto iter = cache.rbegin();
192     ASSERT_TRUE(iter != cache.rend());
193     EXPECT_EQ(kItem1Key, iter->first);
194     EXPECT_EQ(item1.value, iter->second.value);
195   }
196 }
197 
TYPED_TEST(LRUCacheTest,KeyReplacement)198 TYPED_TEST(LRUCacheTest, KeyReplacement) {
199   typedef typename TypeParam::template Type<int, CachedItem> Cache;
200   Cache cache(Cache::NO_AUTO_EVICT);
201 
202   static const int kItem1Key = 1;
203   CachedItem item1(10);
204   cache.Put(kItem1Key, item1);
205 
206   static const int kItem2Key = 2;
207   CachedItem item2(20);
208   cache.Put(kItem2Key, item2);
209 
210   static const int kItem3Key = 3;
211   CachedItem item3(30);
212   cache.Put(kItem3Key, item3);
213 
214   static const int kItem4Key = 4;
215   CachedItem item4(40);
216   cache.Put(kItem4Key, item4);
217 
218   CachedItem item5(50);
219   cache.Put(kItem3Key, item5);
220 
221   EXPECT_EQ(4U, cache.size());
222   for (int i = 0; i < 3; ++i) {
223     auto iter = cache.rbegin();
224     ASSERT_TRUE(iter != cache.rend());
225   }
226 
227   // Make it so only the most important element is there.
228   cache.ShrinkToSize(1);
229 
230   auto iter = cache.begin();
231   EXPECT_EQ(kItem3Key, iter->first);
232   EXPECT_EQ(item5.value, iter->second.value);
233 }
234 
235 // Make sure that the cache release its pointers properly.
TYPED_TEST(LRUCacheTest,Owning)236 TYPED_TEST(LRUCacheTest, Owning) {
237   using Cache =
238       typename TypeParam::template Type<int, std::unique_ptr<CachedItem>>;
239   Cache cache(Cache::NO_AUTO_EVICT);
240 
241   int initial_count = cached_item_live_count;
242 
243   // First insert and item and then overwrite it.
244   static const int kItem1Key = 1;
245   cache.Put(kItem1Key, std::make_unique<CachedItem>(20));
246   cache.Put(kItem1Key, std::make_unique<CachedItem>(22));
247 
248   // There should still be one item, and one extra live item.
249   auto iter = cache.Get(kItem1Key);
250   EXPECT_EQ(1U, cache.size());
251   EXPECT_TRUE(iter != cache.end());
252   EXPECT_EQ(initial_count + 1, cached_item_live_count);
253 
254   // Now remove it.
255   cache.Erase(cache.begin());
256   EXPECT_EQ(initial_count, cached_item_live_count);
257 
258   // Now try another cache that goes out of scope to make sure its pointers
259   // go away.
260   {
261     Cache cache2(Cache::NO_AUTO_EVICT);
262     cache2.Put(1, std::make_unique<CachedItem>(20));
263     cache2.Put(2, std::make_unique<CachedItem>(20));
264   }
265 
266   // There should be no objects leaked.
267   EXPECT_EQ(initial_count, cached_item_live_count);
268 
269   // Check that Clear() also frees things correctly.
270   {
271     Cache cache2(Cache::NO_AUTO_EVICT);
272     cache2.Put(1, std::make_unique<CachedItem>(20));
273     cache2.Put(2, std::make_unique<CachedItem>(20));
274     EXPECT_EQ(initial_count + 2, cached_item_live_count);
275     cache2.Clear();
276     EXPECT_EQ(initial_count, cached_item_live_count);
277   }
278 }
279 
TYPED_TEST(LRUCacheTest,AutoEvict)280 TYPED_TEST(LRUCacheTest, AutoEvict) {
281   using Cache =
282       typename TypeParam::template Type<int, std::unique_ptr<CachedItem>>;
283   static const typename Cache::size_type kMaxSize = 3;
284 
285   int initial_count = cached_item_live_count;
286 
287   {
288     Cache cache(kMaxSize);
289 
290     static const int kItem1Key = 1, kItem2Key = 2, kItem3Key = 3, kItem4Key = 4;
291     cache.Put(kItem1Key, std::make_unique<CachedItem>(20));
292     cache.Put(kItem2Key, std::make_unique<CachedItem>(21));
293     cache.Put(kItem3Key, std::make_unique<CachedItem>(22));
294     cache.Put(kItem4Key, std::make_unique<CachedItem>(23));
295 
296     // The cache should only have kMaxSize items in it even though we inserted
297     // more.
298     EXPECT_EQ(kMaxSize, cache.size());
299   }
300 
301   // There should be no objects leaked.
302   EXPECT_EQ(initial_count, cached_item_live_count);
303 }
304 
TYPED_TEST(LRUCacheTest,HashingLRUCache)305 TYPED_TEST(LRUCacheTest, HashingLRUCache) {
306   // Very simple test to make sure that the hashing cache works correctly.
307   typedef typename TypeParam::template Type<std::string, CachedItem> Cache;
308   Cache cache(Cache::NO_AUTO_EVICT);
309 
310   CachedItem one(1);
311   cache.Put("First", one);
312 
313   CachedItem two(2);
314   cache.Put("Second", two);
315 
316   EXPECT_EQ(one.value, cache.Get("First")->second.value);
317   EXPECT_EQ(two.value, cache.Get("Second")->second.value);
318   cache.ShrinkToSize(1);
319   EXPECT_EQ(two.value, cache.Get("Second")->second.value);
320   EXPECT_TRUE(cache.Get("First") == cache.end());
321 }
322 
TYPED_TEST(LRUCacheTest,Swap)323 TYPED_TEST(LRUCacheTest, Swap) {
324   typedef typename TypeParam::template Type<int, CachedItem> Cache;
325   Cache cache1(Cache::NO_AUTO_EVICT);
326 
327   // Insert two items into cache1.
328   static const int kItem1Key = 1;
329   CachedItem item1(2);
330   auto inserted_item = cache1.Put(kItem1Key, item1);
331   EXPECT_EQ(1U, cache1.size());
332 
333   static const int kItem2Key = 3;
334   CachedItem item2(4);
335   cache1.Put(kItem2Key, item2);
336   EXPECT_EQ(2U, cache1.size());
337 
338   // Verify cache1's elements.
339   {
340     auto iter = cache1.begin();
341     ASSERT_TRUE(iter != cache1.end());
342     EXPECT_EQ(kItem2Key, iter->first);
343     EXPECT_EQ(item2.value, iter->second.value);
344 
345     ++iter;
346     ASSERT_TRUE(iter != cache1.end());
347     EXPECT_EQ(kItem1Key, iter->first);
348     EXPECT_EQ(item1.value, iter->second.value);
349   }
350 
351   // Create another cache2.
352   Cache cache2(Cache::NO_AUTO_EVICT);
353 
354   // Insert three items into cache2.
355   static const int kItem3Key = 5;
356   CachedItem item3(6);
357   inserted_item = cache2.Put(kItem3Key, item3);
358   EXPECT_EQ(1U, cache2.size());
359 
360   static const int kItem4Key = 7;
361   CachedItem item4(8);
362   cache2.Put(kItem4Key, item4);
363   EXPECT_EQ(2U, cache2.size());
364 
365   static const int kItem5Key = 9;
366   CachedItem item5(10);
367   cache2.Put(kItem5Key, item5);
368   EXPECT_EQ(3U, cache2.size());
369 
370   // Verify cache2's elements.
371   {
372     auto iter = cache2.begin();
373     ASSERT_TRUE(iter != cache2.end());
374     EXPECT_EQ(kItem5Key, iter->first);
375     EXPECT_EQ(item5.value, iter->second.value);
376 
377     ++iter;
378     ASSERT_TRUE(iter != cache2.end());
379     EXPECT_EQ(kItem4Key, iter->first);
380     EXPECT_EQ(item4.value, iter->second.value);
381 
382     ++iter;
383     ASSERT_TRUE(iter != cache2.end());
384     EXPECT_EQ(kItem3Key, iter->first);
385     EXPECT_EQ(item3.value, iter->second.value);
386   }
387 
388   // Swap cache1 and cache2 and verify cache2 has cache1's elements and cache1
389   // has cache2's elements.
390   cache2.Swap(cache1);
391 
392   EXPECT_EQ(3U, cache1.size());
393   EXPECT_EQ(2U, cache2.size());
394 
395   // Verify cache1's elements.
396   {
397     auto iter = cache1.begin();
398     ASSERT_TRUE(iter != cache1.end());
399     EXPECT_EQ(kItem5Key, iter->first);
400     EXPECT_EQ(item5.value, iter->second.value);
401 
402     ++iter;
403     ASSERT_TRUE(iter != cache1.end());
404     EXPECT_EQ(kItem4Key, iter->first);
405     EXPECT_EQ(item4.value, iter->second.value);
406 
407     ++iter;
408     ASSERT_TRUE(iter != cache1.end());
409     EXPECT_EQ(kItem3Key, iter->first);
410     EXPECT_EQ(item3.value, iter->second.value);
411   }
412 
413   // Verify cache2's elements.
414   {
415     auto iter = cache2.begin();
416     ASSERT_TRUE(iter != cache2.end());
417     EXPECT_EQ(kItem2Key, iter->first);
418     EXPECT_EQ(item2.value, iter->second.value);
419 
420     ++iter;
421     ASSERT_TRUE(iter != cache2.end());
422     EXPECT_EQ(kItem1Key, iter->first);
423     EXPECT_EQ(item1.value, iter->second.value);
424   }
425 }
426 
TYPED_TEST(LRUCacheSetTest,SetTest)427 TYPED_TEST(LRUCacheSetTest, SetTest) {
428   typedef typename TypeParam::template Type<std::string> Cache;
429   Cache cache(Cache::NO_AUTO_EVICT);
430 
431   cache.Put("Hello");
432   cache.Put("world");
433   cache.Put("foo");
434   cache.Put("bar");
435 
436   // Insert a duplicate element
437   cache.Put("foo");
438 
439   // Iterate from oldest to newest
440   auto r_iter = cache.rbegin();
441   EXPECT_EQ(*r_iter, "Hello");
442   ++r_iter;
443   EXPECT_EQ(*r_iter, "world");
444   ++r_iter;
445   EXPECT_EQ(*r_iter, "bar");
446   ++r_iter;
447   EXPECT_EQ(*r_iter, "foo");
448   ++r_iter;
449   EXPECT_EQ(r_iter, cache.rend());
450 
451   // Iterate from newest to oldest
452   auto iter = cache.begin();
453   EXPECT_EQ(*iter, "foo");
454   ++iter;
455   EXPECT_EQ(*iter, "bar");
456   ++iter;
457   EXPECT_EQ(*iter, "world");
458   ++iter;
459   EXPECT_EQ(*iter, "Hello");
460   ++iter;
461   EXPECT_EQ(iter, cache.end());
462 }
463 
464 // Generalized dereference function. For the base case, this is the identity
465 // function.
466 template <typename T>
467 struct Deref {
468   using Target = T;
derefbase::Deref469   static const Target& deref(const T& x) { return x; }
470 };
471 
472 // `RefCountedData` wraps a type in an interface that supports refcounting.
473 // Deref this as the wrapped type.
474 template <typename T>
475 struct Deref<RefCountedData<T>> {
476   using Target = typename Deref<T>::Target;
derefbase::Deref477   static const Target& deref(const RefCountedData<T>& x) {
478     return Deref<T>::deref(x.data);
479   }
480 };
481 
482 // `scoped_refptr` is a smart pointer that implements reference counting.
483 // Deref this as the pointee.
484 template <typename T>
485 struct Deref<scoped_refptr<T>> {
486   using Target = typename Deref<T>::Target;
derefbase::Deref487   static const Target& deref(const scoped_refptr<T>& x) {
488     return Deref<T>::deref(*x);
489   }
490 };
491 
492 // Implementation of a `std::less`-like type that dereferences the given values
493 // before comparison.
494 template <typename T>
495 struct DerefCompare {
operator ()base::DerefCompare496   bool operator()(const T& lhs, const T& rhs) const {
497     return Deref<T>::deref(lhs) < Deref<T>::deref(rhs);
498   }
499 };
500 
501 // Implementation of a `std::equal_to`-like type that dereferences the given
502 // values before comparison.
503 template <typename T>
504 struct DerefEqual {
operator ()base::DerefEqual505   bool operator()(const T& lhs, const T& rhs) const {
506     return Deref<T>::deref(lhs) == Deref<T>::deref(rhs);
507   }
508 };
509 
510 // Implementation of a `std::hash`-like type that dereferences the given value
511 // before calculating the hash.
512 template <typename T, template <class> typename HashT = std::hash>
513 struct DerefHash {
operator ()base::DerefHash514   size_t operator()(const T& x) const {
515     return HashT<typename Deref<T>::Target>()(Deref<T>::deref(x));
516   }
517 };
518 
519 // This tests that upon replacing a duplicate element in the cache with `Put`,
520 // the element's identity is replaced as well.
TYPED_TEST(LRUCacheSetTest,ReplacementIdentity)521 TYPED_TEST(LRUCacheSetTest, ReplacementIdentity) {
522   using Item = RefCountedData<std::string>;
523   using Ptr = scoped_refptr<Item>;
524 
525   // Helper to create the correct type of base::*LRUCacheSet, since they have
526   // different template arguments.
527   constexpr auto kCreateCache = []() {
528     if constexpr (std::is_same_v<TypeParam, LRUCacheSetTemplate>) {
529       using Cache = typename TypeParam::template Type<Ptr, DerefCompare<Ptr>>;
530       return Cache(Cache::NO_AUTO_EVICT);
531     } else if constexpr (std::is_same_v<TypeParam,
532                                         HashingLRUCacheSetTemplate>) {
533       using Cache = typename TypeParam::template Type<Ptr, DerefHash<Ptr>,
534                                                       DerefEqual<Ptr>>;
535       return Cache(Cache::NO_AUTO_EVICT);
536     } else {
537       static_assert(!sizeof(TypeParam),
538                     "This test was only written to support "
539                     "`LRUCacheSetTemplate` and `HashingLRUCacheSetTemplate`");
540     }
541   };
542 
543   auto cache = kCreateCache();
544   cache.Put(MakeRefCounted<Item>("Hello"));
545   cache.Put(MakeRefCounted<Item>("world"));
546   cache.Put(MakeRefCounted<Item>("foo"));
547   cache.Put(MakeRefCounted<Item>("bar"));
548 
549   // Insert a duplicate element
550   {
551     auto foo = MakeRefCounted<Item>("foo");
552     const auto* new_foo_addr = foo.get();
553     const auto* old_foo_addr = cache.Peek(foo)->get();
554     auto iter = cache.Put(std::move(foo));
555     EXPECT_EQ(iter->get(), new_foo_addr);
556     EXPECT_NE(iter->get(), old_foo_addr);
557   }
558 
559   // Iterate from oldest to newest
560   auto r_iter = cache.rbegin();
561   EXPECT_EQ((*r_iter)->data, "Hello");
562   ++r_iter;
563   EXPECT_EQ((*r_iter)->data, "world");
564   ++r_iter;
565   EXPECT_EQ((*r_iter)->data, "bar");
566   ++r_iter;
567   EXPECT_EQ((*r_iter)->data, "foo");
568   ++r_iter;
569   EXPECT_EQ(r_iter, cache.rend());
570 
571   // Iterate from newest to oldest
572   auto iter = cache.begin();
573   EXPECT_EQ((*iter)->data, "foo");
574   ++iter;
575   EXPECT_EQ((*iter)->data, "bar");
576   ++iter;
577   EXPECT_EQ((*iter)->data, "world");
578   ++iter;
579   EXPECT_EQ((*iter)->data, "Hello");
580   ++iter;
581   EXPECT_EQ(iter, cache.end());
582 }
583 
584 #if BUILDFLAG(ENABLE_BASE_TRACING)
TYPED_TEST(LRUCacheTest,EstimateMemory)585 TYPED_TEST(LRUCacheTest, EstimateMemory) {
586   typedef typename TypeParam::template Type<std::string, int> Cache;
587   Cache cache(10);
588 
589   const std::string key(100u, 'a');
590   cache.Put(key, 1);
591 
592   EXPECT_GT(trace_event::EstimateMemoryUsage(cache),
593             trace_event::EstimateMemoryUsage(key));
594 }
595 #endif  // BUILDFLAG(ENABLE_BASE_TRACING)
596 
597 }  // namespace base
598