blob: 62b03dd2e1216c0ec848474fdf270404053f5210 [file] [log] [blame]
/*
* 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 <chrono>
#include <limits>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "common/lru_cache.h"
namespace testing {
using bluetooth::common::LruCache;
TEST(LruCacheTest, empty_test) {
LruCache<int, int> 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<int, int> cache_1(2);
cache_1.insert_or_assign(1, 10);
cache_1.insert_or_assign(2, 20);
LruCache<int, int> 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<int, int> 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<int, int> cache_4(2);
EXPECT_NE(cache_1, cache_4);
// Empty caches should be equal
LruCache<int, int> cache_5(2);
EXPECT_EQ(cache_4, cache_5);
// Empty caches with different capacity should not be equal
LruCache<int, int> cache_6(3);
EXPECT_NE(cache_4, cache_6);
}
TEST(LruCacheTest, try_emplace_test) {
LruCache<int, int> 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<int, std::shared_ptr<int>> cache(2);
cache.insert_or_assign(1, std::make_shared<int>(100));
auto iter = cache.find(1);
EXPECT_EQ(*iter->second, 100);
LruCache<int, std::shared_ptr<int>> 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<int, std::shared_ptr<int>> cache(2);
cache.insert_or_assign(1, std::make_shared<int>(100));
auto iter = cache.find(1);
EXPECT_EQ(*iter->second, 100);
LruCache<int, std::shared_ptr<int>> 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<int, std::unique_ptr<int>> cache(2);
cache.insert_or_assign(1, std::make_unique<int>(100));
auto iter = cache.find(1);
EXPECT_EQ(*iter->second, 100);
cache.insert_or_assign(1, std::make_unique<int>(400));
iter = cache.find(1);
EXPECT_EQ(*iter->second, 400);
}
TEST(LruCacheTest, move_insert_cache_test) {
LruCache<int, LruCache<int, int>> cache(2);
LruCache<int, int> 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<int, int> 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<int, int> 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<int, int> 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<int, int> 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<int, int> 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<int, int>::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<int, int> 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<int, int>::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<int, int> 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<int, int> 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<int, int> 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<int, int> 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<int, int> 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<int, int> lru_cache(2);
lru_cache.insert_or_assign(1, 10);
lru_cache.insert_or_assign(2, 20);
// Warm elements first
std::list<std::pair<int, int>> list(lru_cache.begin(), lru_cache.end());
ASSERT_THAT(list, ElementsAre(Pair(2, 20), Pair(1, 10)));
}
TEST(LruCacheTest, for_loop_test) {
LruCache<int, int> lru_cache(2);
lru_cache.insert_or_assign(1, 10);
lru_cache.insert_or_assign(2, 20);
// Warm elements first
std::list<std::pair<int, int>> 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<int, int> cache(static_cast<size_t>(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<int, int>::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<std::chrono::microseconds>(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