1#include <userver/multi-index-lru/container.hpp>
2#include <userver/utest/utest.hpp>
6#include <boost/multi_index/hashed_index.hpp>
7#include <boost/multi_index/identity.hpp>
8#include <boost/multi_index/member.hpp>
9#include <boost/multi_index/ordered_index.hpp>
11USERVER_NAMESPACE_BEGIN
15class LRUUsersTest :
public ::testing::Test {
17 void SetUp()
override {}
28 bool operator==(
const User& other)
const {
29 return id == other.id && email == other.email && name == other.name;
33 using UserCache = multi_index_lru::
Container<
35 boost::multi_index::indexed_by<
36 boost::multi_index::ordered_unique<
37 boost::multi_index::tag<IdTag>,
38 boost::multi_index::member<User,
int, &User::id>>,
39 boost::multi_index::ordered_unique<
40 boost::multi_index::tag<EmailTag>,
41 boost::multi_index::member<User, std::string, &User::email>>,
42 boost::multi_index::ordered_non_unique<
43 boost::multi_index::tag<NameTag>,
44 boost::multi_index::member<User, std::string, &User::name>>>>;
47UTEST_F(LRUUsersTest, BasicOperations) {
51 cache.emplace(User{1,
"alice@test.com",
"Alice"});
52 cache.emplace(User{2,
"bob@test.com",
"Bob"});
53 cache.emplace(User{3,
"charlie@test.com",
"Charlie"});
55 EXPECT_EQ(cache.size(), 3);
58 auto by_id = cache.find<IdTag,
int>(1);
59 EXPECT_NE(by_id, cache.end<IdTag>());
60 EXPECT_EQ(by_id->name,
"Alice");
63 auto by_email = cache.find<EmailTag, std::string>(
"bob@test.com");
64 EXPECT_NE(by_email, cache.end<EmailTag>());
65 EXPECT_EQ(by_email->id, 2);
68 auto by_name = cache.find<NameTag, std::string>(
"Charlie");
69 EXPECT_NE(by_name, cache.end<NameTag>());
70 EXPECT_EQ(by_name->email,
"charlie@test.com");
72 auto it = cache.find<EmailTag, std::string>(
"alice@test.com");
73 EXPECT_NE(it, cache.end<EmailTag>());
76UTEST_F(LRUUsersTest, LRUEviction) {
79 cache.emplace(User{1,
"alice@test.com",
"Alice"});
80 cache.emplace(User{2,
"bob@test.com",
"Bob"});
81 cache.emplace(User{3,
"charlie@test.com",
"Charlie"});
88 cache.emplace(User{4,
"david@test.com",
"David"});
90 EXPECT_FALSE((cache.contains<IdTag>(2)));
91 EXPECT_TRUE((cache.contains<IdTag>(1)));
92 EXPECT_TRUE((cache.contains<IdTag>(3)));
93 EXPECT_TRUE((cache.contains<IdTag>(4)));
96UTEST_F(LRUUsersTest, GetNoUpdateDoesNotChangeLru) {
99 cache.emplace(User{1,
"alice@test.com",
"Alice"});
100 cache.emplace(User{2,
"bob@test.com",
"Bob"});
101 cache.emplace(User{3,
"charlie@test.com",
"Charlie"});
103 auto it = cache.find_no_update<IdTag>(1);
104 EXPECT_NE(it, cache.end<IdTag>());
105 EXPECT_EQ(it->name,
"Alice");
107 cache.emplace(User{4,
"david@test.com",
"David"});
109 EXPECT_FALSE((cache.contains<IdTag>(1)));
110 EXPECT_TRUE((cache.contains<IdTag>(2)));
111 EXPECT_TRUE((cache.contains<IdTag>(3)));
112 EXPECT_TRUE((cache.contains<IdTag>(4)));
115UTEST_F(LRUUsersTest, EqualRangeUpdatesLruForAllMatches) {
118 cache.emplace(User{1,
"john1@test.com",
"John"});
119 cache.emplace(User{2,
"john2@test.com",
"John"});
120 cache.emplace(User{3,
"alice@test.com",
"Alice"});
121 cache.emplace(User{4,
"bob@test.com",
"Bob"});
123 auto [begin, end] = cache.equal_range<NameTag, std::string>(
"John");
125 for (
auto it = begin; it != end; ++it) {
130 cache.emplace(User{5,
"eve@test.com",
"Eve"});
132 EXPECT_TRUE((cache.contains<IdTag>(1)));
133 EXPECT_TRUE((cache.contains<IdTag>(2)));
134 EXPECT_FALSE((cache.contains<IdTag>(3)));
135 EXPECT_TRUE((cache.contains<IdTag>(4)));
136 EXPECT_TRUE((cache.contains<IdTag>(5)));
139UTEST_F(LRUUsersTest, EqualRangeNoUpdateDoesNotChangeLru) {
142 cache.emplace(User{1,
"john1@test.com",
"John"});
143 cache.emplace(User{2,
"john2@test.com",
"John"});
144 cache.emplace(User{3,
"alice@test.com",
"Alice"});
145 cache.emplace(User{4,
"bob@test.com",
"Bob"});
147 auto [begin, end] = cache.equal_range_no_update<NameTag, std::string>(
"John");
149 for (
auto it = begin; it != end; ++it) {
154 cache.emplace(User{5,
"eve@test.com",
"Eve"});
156 EXPECT_FALSE((cache.contains<IdTag>(1)));
157 EXPECT_TRUE((cache.contains<IdTag>(2)));
158 EXPECT_TRUE((cache.contains<IdTag>(3)));
159 EXPECT_TRUE((cache.contains<IdTag>(4)));
160 EXPECT_TRUE((cache.contains<IdTag>(5)));
163UTEST_F(LRUUsersTest, EqualRangeWorksWithEmptyRange) {
165 cache.emplace(User{1,
"alice@test.com",
"Alice"});
167 auto [begin, end] = cache.equal_range<NameTag, std::string>(
"Nonexistent");
168 EXPECT_EQ(begin, end);
171UTEST_F(LRUUsersTest, EqualRangeNoUpdateWorksWithEmptyRange) {
173 cache.emplace(User{1,
"alice@test.com",
"Alice"});
175 auto [begin, end] = cache.equal_range_no_update<NameTag, std::string>(
"Nonexistent");
176 EXPECT_EQ(begin, end);
179class ProductsTest :
public ::testing::Test {
189 bool operator==(
const Product& other)
const {
190 return sku == other.sku && name == other.name && price == other.price;
194 using ProductCache = multi_index_lru::
Container<
196 boost::multi_index::indexed_by<
197 boost::multi_index::ordered_unique<
198 boost::multi_index::tag<SkuTag>,
199 boost::multi_index::member<Product, std::string, &Product::sku>>,
200 boost::multi_index::ordered_unique<
201 boost::multi_index::tag<NameTag>,
202 boost::multi_index::member<Product, std::string, &Product::name>>>>;
205UTEST_F(ProductsTest, BasicProductOperations) {
206 ProductCache cache(2);
208 cache.emplace(Product{
"A1",
"Laptop", 999.99});
209 cache.emplace(Product{
"A2",
"Mouse", 29.99});
211 auto laptop = cache.find<SkuTag, std::string>(
"A1");
212 EXPECT_NE(laptop, cache.end<SkuTag>());
213 EXPECT_EQ(laptop->name,
"Laptop");
216UTEST_F(ProductsTest, ProductEviction) {
217 ProductCache cache(2);
219 cache.emplace(Product{
"A1",
"Laptop", 999.99});
220 cache.emplace(Product{
"A2",
"Mouse", 29.99});
223 cache.find<SkuTag>(
"A1");
224 cache.emplace(Product{
"A3",
"Keyboard", 79.99});
226 EXPECT_TRUE((cache.contains<SkuTag, std::string>(
"A1")));
227 EXPECT_TRUE((cache.contains<SkuTag, std::string>(
"A3")));
228 EXPECT_FALSE((cache.contains<SkuTag, std::string>(
"A2")));
230 EXPECT_NE(cache.find<NameTag>(
"Keyboard"), cache.end<NameTag>());
231 EXPECT_EQ(cache.find<NameTag>(
"Mouse"), cache.end<NameTag>());
234class ProductsTestWithAllocator :
public ProductsTest {
238 static std::atomic<size_t> count;
239 static void increment() { count++; }
240 static size_t get() {
return count.load(); }
241 static void reset() { count = 0; }
244 template <
typename T>
245 class CountingAllocator :
public std::allocator<T> {
247 CountingAllocator() =
default;
248 template <
typename U>
249 CountingAllocator(
const CountingAllocator<U>&) {}
251 T* allocate(size_t n) {
252 Counter::increment();
253 return std::allocator<T>::allocate(n);
256 static size_t get_count() {
return Counter::get(); }
257 static void reset_count() { Counter::reset(); }
259 template <
typename U>
261 using other = CountingAllocator<U>;
265 using ProductCache = multi_index_lru::
Container<
267 boost::multi_index::indexed_by<
268 boost::multi_index::ordered_unique<
269 boost::multi_index::tag<SkuTag>,
270 boost::multi_index::member<Product, std::string, &Product::sku>>,
271 boost::multi_index::ordered_unique<
272 boost::multi_index::tag<NameTag>,
273 boost::multi_index::member<Product, std::string, &Product::name>>>,
274 CountingAllocator<Product>>;
277std::atomic<size_t> ProductsTestWithAllocator::Counter::count{0};
279UTEST_F(ProductsTestWithAllocator, AllocationsCheck) {
280 ProductCache cache(20);
282 for (
int i = 0; i < 1000; ++i) {
283 cache.insert(Product{
"A" + std::to_string(i),
"Laptop_" + std::to_string(i), 999.99});
285 const auto first_allocations_count = ProductsTestWithAllocator::CountingAllocator<
int>::get_count();
288 for (
int i = 0; i < 1000; ++i) {
289 cache.insert(Product{
"A" + std::to_string(i),
"Laptop_" + std::to_string(i), 999.99});
293 EXPECT_EQ(first_allocations_count, ProductsTestWithAllocator::CountingAllocator<
int>::get_count());
295 cache.insert(Product{
"B_0",
"C_0", 999.99});
296 cache.erase(cache.find<NameTag>(
"C_0"));
297 cache.insert(Product{
"B_1",
"C_1", 999.99});
298 cache.erase(cache.find<SkuTag>(
"B_1"));
299 cache.insert(Product{
"B_2",
"C_2", 999.99});
302 EXPECT_EQ(first_allocations_count, ProductsTestWithAllocator::CountingAllocator<
int>::get_count());
305UTEST(Snippet, SimpleUsage) {
313 MyValueT my_value{
"some_key", 1};
315 using MyLruCache = multi_index_lru::
Container<
317 boost::multi_index::indexed_by<boost::multi_index::hashed_unique<
318 boost::multi_index::tag<MyTag>,
319 boost::multi_index::member<MyValueT, std::string, &MyValueT::key>>>>;
321 MyLruCache cache(1000);
322 cache.insert(my_value);
323 auto it = cache.find<MyTag>(
"some_key");
324 EXPECT_NE(it, cache.end<MyTag>());