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