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