/* * Copyright 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include "common/lru_cache.h" namespace testing { using bluetooth::common::LruCache; TEST(LruCacheTest, empty_test) { LruCache cache(3); // capacity = 3; EXPECT_EQ(cache.size(), 0); EXPECT_EQ(cache.find(42), cache.end()); cache.clear(); // should not crash EXPECT_EQ(cache.find(42), cache.end()); EXPECT_FALSE(cache.contains(42)); EXPECT_FALSE(cache.extract(42)); } TEST(LruCacheTest, comparison_test) { LruCache cache_1(2); cache_1.insert_or_assign(1, 10); cache_1.insert_or_assign(2, 20); LruCache cache_2(2); cache_2.insert_or_assign(1, 10); cache_2.insert_or_assign(2, 20); EXPECT_EQ(cache_1, cache_2); // Cache with different order should not be equal cache_2.find(1); EXPECT_NE(cache_1, cache_2); cache_1.find(1); EXPECT_EQ(cache_1, cache_2); // Cache with different value should be different cache_2.insert_or_assign(1, 11); EXPECT_NE(cache_1, cache_2); // Cache with different capacity should not be equal LruCache cache_3(3); cache_3.insert_or_assign(1, 10); cache_3.insert_or_assign(2, 20); EXPECT_NE(cache_1, cache_3); // Empty cache should not be equal to non-empty ones LruCache cache_4(2); EXPECT_NE(cache_1, cache_4); // Empty caches should be equal LruCache cache_5(2); EXPECT_EQ(cache_4, cache_5); // Empty caches with different capacity should not be equal LruCache cache_6(3); EXPECT_NE(cache_4, cache_6); } TEST(LruCacheTest, try_emplace_test) { LruCache cache(2); cache.insert_or_assign(1, 10); cache.insert_or_assign(2, 20); auto result = cache.try_emplace(42, 420); // 1, 10 evicted EXPECT_EQ(std::get<2>(result), std::make_pair(1, 10)); auto iter = cache.find(42); EXPECT_EQ(iter->second, 420); EXPECT_EQ(iter, std::get<0>(result)); ASSERT_THAT(cache, ElementsAre(Pair(42, 420), Pair(2, 20))); } TEST(LruCacheTest, copy_test) { LruCache> cache(2); cache.insert_or_assign(1, std::make_shared(100)); auto iter = cache.find(1); EXPECT_EQ(*iter->second, 100); LruCache> new_cache = cache; iter = new_cache.find(1); EXPECT_EQ(*iter->second, 100); *iter->second = 300; iter = new_cache.find(1); EXPECT_EQ(*iter->second, 300); // Since copy is used, shared_ptr should increase count EXPECT_EQ(iter->second.use_count(), 2); } TEST(LruCacheTest, move_test) { LruCache> cache(2); cache.insert_or_assign(1, std::make_shared(100)); auto iter = cache.find(1); EXPECT_EQ(*iter->second, 100); LruCache> new_cache = std::move(cache); iter = new_cache.find(1); EXPECT_EQ(*iter->second, 100); *iter->second = 300; iter = new_cache.find(1); EXPECT_EQ(*iter->second, 300); // Since move is used, shared_ptr should not increase count EXPECT_EQ(iter->second.use_count(), 1); } TEST(LruCacheTest, move_insert_unique_ptr_test) { LruCache> cache(2); cache.insert_or_assign(1, std::make_unique(100)); auto iter = cache.find(1); EXPECT_EQ(*iter->second, 100); cache.insert_or_assign(1, std::make_unique(400)); iter = cache.find(1); EXPECT_EQ(*iter->second, 400); } TEST(LruCacheTest, move_insert_cache_test) { LruCache> cache(2); LruCache m1(2); m1.insert_or_assign(1, 100); cache.insert_or_assign(1, std::move(m1)); auto iter = cache.find(1); EXPECT_THAT(iter->second, ElementsAre(Pair(1, 100))); LruCache m2(2); m2.insert_or_assign(2, 200); cache.insert_or_assign(1, std::move(m2)); iter = cache.find(1); EXPECT_THAT(iter->second, ElementsAre(Pair(2, 200))); } TEST(LruCacheTest, erase_one_item_test) { LruCache cache(3); cache.insert_or_assign(1, 10); cache.insert_or_assign(2, 20); cache.insert_or_assign(3, 30); auto iter = cache.find(2); // 2, 3, 1 cache.find(3); // 3, 2, 1 iter = cache.erase(iter); EXPECT_EQ(iter->first, 1); EXPECT_EQ(iter->second, 10); EXPECT_THAT(cache, ElementsAre(Pair(3, 30), Pair(1, 10))); } TEST(LruCacheTest, erase_in_for_loop_test) { LruCache cache(3); cache.insert_or_assign(1, 10); cache.insert_or_assign(2, 20); cache.insert_or_assign(3, 30); for (auto iter = cache.begin(); iter != cache.end();) { if (iter->first == 2) { iter = cache.erase(iter); } else { ++iter; } } EXPECT_THAT(cache, ElementsAre(Pair(3, 30), Pair(1, 10))); } TEST(LruCacheTest, get_and_contains_key_test) { LruCache cache(3); // capacity = 3; EXPECT_EQ(cache.size(), 0); EXPECT_EQ(cache.find(42), cache.end()); EXPECT_FALSE(cache.contains(42)); EXPECT_FALSE(cache.insert_or_assign(56, 200)); EXPECT_EQ(cache.find(42), cache.end()); EXPECT_FALSE(cache.contains(42)); EXPECT_NE(cache.find(56), cache.end()); EXPECT_TRUE(cache.contains(56)); auto iter = cache.find(56); EXPECT_NE(iter, cache.end()); EXPECT_EQ(iter->second, 200); EXPECT_TRUE(cache.extract(56)); EXPECT_FALSE(cache.contains(56)); } TEST(LruCacheTest, put_and_get_sequence_1) { // Section 1: Ordered put and ordered get LruCache cache(3); // capacity = 3; EXPECT_FALSE(cache.insert_or_assign(1, 10)); EXPECT_EQ(cache.size(), 1); EXPECT_FALSE(cache.insert_or_assign(2, 20)); EXPECT_EQ(cache.size(), 2); EXPECT_FALSE(cache.insert_or_assign(3, 30)); EXPECT_EQ(cache.size(), 3); // 3, 2, 1 after above operations auto evicted = cache.insert_or_assign(4, 40); // 4, 3, 2 after above operations, 1 is evicted EXPECT_TRUE(evicted); EXPECT_EQ(*evicted, std::make_pair(1, 10)); EXPECT_EQ(cache.find(1), cache.end()); LruCache::const_iterator iter; EXPECT_NE(iter = cache.find(4), cache.end()); EXPECT_EQ(iter->second, 40); EXPECT_NE(iter = cache.find(2), cache.end()); EXPECT_EQ(iter->second, 20); EXPECT_NE(iter = cache.find(3), cache.end()); EXPECT_EQ(iter->second, 30); // 3, 2, 4 after above operations // Section 2: Over capacity put and ordered get evicted = cache.insert_or_assign(5, 50); // 5, 3, 2 after above operations, 4 is evicted EXPECT_EQ(cache.size(), 3); EXPECT_TRUE(evicted); EXPECT_EQ(*evicted, std::make_pair(4, 40)); EXPECT_TRUE(cache.extract(3)); // 5, 2 should be in cache, 3 is removed EXPECT_FALSE(cache.insert_or_assign(6, 60)); // 6, 5, 2 should be in cache // Section 3: Out of order get EXPECT_EQ(cache.find(3), cache.end()); EXPECT_EQ(cache.find(4), cache.end()); EXPECT_NE(iter = cache.find(2), cache.end()); // 2, 6, 5 should be in cache EXPECT_EQ(iter->second, 20); EXPECT_NE(iter = cache.find(6), cache.end()); // 6, 2, 5 should be in cache EXPECT_EQ(iter->second, 60); EXPECT_NE(iter = cache.find(5), cache.end()); // 5, 6, 2 should be in cache EXPECT_EQ(iter->second, 50); evicted = cache.insert_or_assign(7, 70); // 7, 5, 6 should be in cache, 2 is evicted EXPECT_TRUE(evicted); EXPECT_EQ(*evicted, std::make_pair(2, 20)); } TEST(LruCacheTest, put_and_get_sequence_2) { // Section 1: Replace item in cache LruCache cache(2); // size = 2; EXPECT_FALSE(cache.insert_or_assign(1, 10)); EXPECT_FALSE(cache.insert_or_assign(2, 20)); // 2, 1 in cache auto evicted = cache.insert_or_assign(3, 30); // 3, 2 in cache, 1 is evicted EXPECT_TRUE(evicted); EXPECT_EQ(*evicted, std::make_pair(1, 10)); EXPECT_FALSE(cache.insert_or_assign(2, 200)); // 2, 3 in cache, nothing is evicted EXPECT_EQ(cache.size(), 2); EXPECT_FALSE(cache.contains(1)); LruCache::const_iterator iter; EXPECT_NE(iter = cache.find(2), cache.end()); EXPECT_EQ(iter->second, 200); EXPECT_NE(iter = cache.find(3), cache.end()); // 3, 2 in cache EXPECT_EQ(iter->second, 30); evicted = cache.insert_or_assign(4, 40); // 4, 3 in cache, 2 is evicted EXPECT_TRUE(evicted); EXPECT_EQ(*evicted, std::make_pair(2, 200)); EXPECT_FALSE(cache.contains(2)); EXPECT_NE(iter = cache.find(3), cache.end()); EXPECT_EQ(iter->second, 30); EXPECT_NE(iter = cache.find(4), cache.end()); EXPECT_EQ(iter->second, 40); // 4, 3 in cache EXPECT_TRUE(cache.extract(4)); EXPECT_FALSE(cache.contains(4)); // 3 in cache EXPECT_EQ(cache.size(), 1); EXPECT_FALSE(cache.insert_or_assign(2, 2000)); // 2, 3 in cache EXPECT_FALSE(cache.contains(4)); EXPECT_NE(iter = cache.find(3), cache.end()); EXPECT_EQ(iter->second, 30); EXPECT_NE(iter = cache.find(2), cache.end()); EXPECT_EQ(iter->second, 2000); EXPECT_TRUE(cache.extract(2)); EXPECT_TRUE(cache.extract(3)); EXPECT_FALSE(cache.insert_or_assign(5, 50)); EXPECT_FALSE(cache.insert_or_assign(1, 100)); EXPECT_FALSE(cache.insert_or_assign(5, 1000)); EXPECT_EQ(cache.size(), 2); // 5, 1 in cache evicted = cache.insert_or_assign(6, 2000); // 6, 5 in cache EXPECT_TRUE(evicted); EXPECT_EQ(*evicted, std::make_pair(1, 100)); EXPECT_FALSE(cache.contains(2)); EXPECT_FALSE(cache.contains(3)); EXPECT_NE(iter = cache.find(6), cache.end()); EXPECT_EQ(iter->second, 2000); EXPECT_NE(iter = cache.find(5), cache.end()); EXPECT_EQ(iter->second, 1000); } TEST(LruCacheTest, in_place_modification_test) { LruCache cache(2); cache.insert_or_assign(1, 10); cache.insert_or_assign(2, 20); auto iter = cache.find(2); ASSERT_THAT(cache, ElementsAre(Pair(2, 20), Pair(1, 10))); iter->second = 200; ASSERT_THAT(cache, ElementsAre(Pair(2, 200), Pair(1, 10))); cache.insert_or_assign(1, 100); // 1, 2 in cache ASSERT_THAT(cache, ElementsAre(Pair(1, 100), Pair(2, 200))); // modifying iterator does not warm up key iter->second = 400; ASSERT_THAT(cache, ElementsAre(Pair(1, 100), Pair(2, 400))); } TEST(LruCacheTest, get_test) { LruCache cache(2); EXPECT_FALSE(cache.insert_or_assign(1, 10)); EXPECT_FALSE(cache.insert_or_assign(2, 20)); EXPECT_TRUE(cache.contains(1)); // 1, 2 in cache auto evicted = cache.insert_or_assign(3, 30); // 3, 1 in cache EXPECT_TRUE(evicted); EXPECT_EQ(*evicted, std::make_pair(2, 20)); } TEST(LruCacheTest, remove_test) { LruCache cache(10); for (int key = 0; key <= 30; key++) { cache.insert_or_assign(key, key * 100); } for (int key = 0; key <= 20; key++) { EXPECT_FALSE(cache.contains(key)); } for (int key = 21; key <= 30; key++) { EXPECT_TRUE(cache.contains(key)); } for (int key = 0; key <= 20; key++) { EXPECT_FALSE(cache.extract(key)); } for (int key = 21; key <= 30; key++) { auto removed = cache.extract(key); EXPECT_TRUE(removed); EXPECT_EQ(*removed, std::make_pair(key, key * 100)); } for (int key = 21; key <= 30; key++) { EXPECT_FALSE(cache.contains(key)); } } TEST(LruCacheTest, clear_test) { LruCache cache(10); for (int key = 0; key < 10; key++) { cache.insert_or_assign(key, key * 100); } for (int key = 0; key < 10; key++) { EXPECT_TRUE(cache.contains(key)); } cache.clear(); for (int key = 0; key < 10; key++) { EXPECT_FALSE(cache.contains(key)); } for (int key = 0; key < 10; key++) { cache.insert_or_assign(key, key * 1000); } for (int key = 0; key < 10; key++) { EXPECT_TRUE(cache.contains(key)); } } TEST(LruCacheTest, container_test) { LruCache lru_cache(2); lru_cache.insert_or_assign(1, 10); lru_cache.insert_or_assign(2, 20); // Warm elements first ASSERT_THAT(lru_cache, ElementsAre(Pair(2, 20), Pair(1, 10))); } TEST(LruCacheTest, iterator_test) { LruCache lru_cache(2); lru_cache.insert_or_assign(1, 10); lru_cache.insert_or_assign(2, 20); // Warm elements first std::list> list(lru_cache.begin(), lru_cache.end()); ASSERT_THAT(list, ElementsAre(Pair(2, 20), Pair(1, 10))); } TEST(LruCacheTest, for_loop_test) { LruCache lru_cache(2); lru_cache.insert_or_assign(1, 10); lru_cache.insert_or_assign(2, 20); // Warm elements first std::list> list; for (const auto& node : lru_cache) { list.emplace_back(node); } ASSERT_THAT(list, ElementsAre(Pair(2, 20), Pair(1, 10))); list.clear(); for (auto& node : lru_cache) { list.emplace_back(node); node.second = node.second * 2; } ASSERT_THAT(list, ElementsAre(Pair(2, 20), Pair(1, 10))); list.clear(); for (const auto& node : lru_cache) { list.emplace_back(node); } ASSERT_THAT(list, ElementsAre(Pair(2, 40), Pair(1, 20))); } TEST(LruCacheTest, pressure_test) { auto started = std::chrono::high_resolution_clock::now(); int capacity = 0xFFFF; // 2^16 = 65535 LruCache cache(static_cast(capacity)); // fill the cache for (int key = 0; key < capacity; key++) { cache.insert_or_assign(key, key); } // make sure the cache is full for (int key = 0; key < capacity; key++) { EXPECT_TRUE(cache.contains(key)); } // refresh the entire cache for (int key = 0; key < capacity; key++) { int new_key = key + capacity; cache.insert_or_assign(new_key, new_key); EXPECT_FALSE(cache.contains(key)); EXPECT_TRUE(cache.contains(new_key)); } // clear the entire cache LruCache::const_iterator iter; for (int key = capacity; key < 2 * capacity; key++) { EXPECT_NE(iter = cache.find(key), cache.end()); EXPECT_EQ(iter->second, key); EXPECT_TRUE(cache.extract(key)); } EXPECT_EQ(cache.size(), 0); // test execution time auto done = std::chrono::high_resolution_clock::now(); int execution_time = std::chrono::duration_cast(done - started).count(); // Shouldn't be more than 1120ms int execution_time_per_cycle_us = 17; EXPECT_LT(execution_time, execution_time_per_cycle_us * capacity); } } // namespace testing