userver: /data/code/userver/libraries/multi-index-lru/src/container_test.cpp Source File
Loading...
Searching...
No Matches
container_test.cpp
1#include <userver/multi-index-lru/container.hpp>
2#include <userver/utest/utest.hpp>
3
4#include <string>
5
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>
10
11USERVER_NAMESPACE_BEGIN
12
13namespace {
14
15class LRUUsersTest : public ::testing::Test {
16protected:
17 void SetUp() override {}
18
19 struct IdTag {};
20 struct EmailTag {};
21 struct NameTag {};
22
23 struct User {
24 int id;
25 std::string email;
26 std::string name;
27
28 bool operator==(const User& other) const {
29 return id == other.id && email == other.email && name == other.name;
30 }
31 };
32
33 using UserCache = multi_index_lru::Container<
34 User,
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>>>>;
45};
46
47UTEST_F(LRUUsersTest, BasicOperations) {
48 UserCache cache(3); // capacity == 3
49
50 // Test insertion
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"});
54
55 EXPECT_EQ(cache.size(), 3);
56
57 // Test find by id
58 auto by_id = cache.find<IdTag, int>(1);
59 EXPECT_NE(by_id, cache.end<IdTag>());
60 EXPECT_EQ(by_id->name, "Alice");
61
62 // Test find by email
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);
66
67 // Test find by name
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");
71
72 auto it = cache.find<EmailTag, std::string>("alice@test.com");
73 EXPECT_NE(it, cache.end<EmailTag>());
74}
75
76UTEST_F(LRUUsersTest, LRUEviction) {
77 UserCache cache(3);
78
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"});
82
83 // Access Alice and Charlie to make them recently used
84 cache.find<IdTag>(1);
85 cache.find<IdTag>(3);
86
87 // Add fourth element - Bob should be evicted
88 cache.emplace(User{4, "david@test.com", "David"});
89
90 EXPECT_FALSE((cache.contains<IdTag>(2))); // Bob evicted
91 EXPECT_TRUE((cache.contains<IdTag>(1))); // Alice remains
92 EXPECT_TRUE((cache.contains<IdTag>(3))); // Charlie remains
93 EXPECT_TRUE((cache.contains<IdTag>(4))); // David added
94}
95
96UTEST_F(LRUUsersTest, GetNoUpdateDoesNotChangeLru) {
97 UserCache cache(3);
98
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"});
102
103 auto it = cache.find_no_update<IdTag>(1); // without updating
104 EXPECT_NE(it, cache.end<IdTag>());
105 EXPECT_EQ(it->name, "Alice");
106
107 cache.emplace(User{4, "david@test.com", "David"});
108
109 EXPECT_FALSE((cache.contains<IdTag>(1))); // evicted
110 EXPECT_TRUE((cache.contains<IdTag>(2))); // remains
111 EXPECT_TRUE((cache.contains<IdTag>(3))); // remains
112 EXPECT_TRUE((cache.contains<IdTag>(4))); // added
113}
114
115UTEST_F(LRUUsersTest, EqualRangeUpdatesLruForAllMatches) {
116 UserCache cache(4);
117
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"});
122
123 auto [begin, end] = cache.equal_range<NameTag, std::string>("John");
124 int count = 0;
125 for (auto it = begin; it != end; ++it) {
126 ++count;
127 }
128 EXPECT_EQ(count, 2);
129
130 cache.emplace(User{5, "eve@test.com", "Eve"});
131
132 EXPECT_TRUE((cache.contains<IdTag>(1))); // remains
133 EXPECT_TRUE((cache.contains<IdTag>(2))); // remains
134 EXPECT_FALSE((cache.contains<IdTag>(3))); // evicted
135 EXPECT_TRUE((cache.contains<IdTag>(4))); // remains
136 EXPECT_TRUE((cache.contains<IdTag>(5))); // added
137}
138
139UTEST_F(LRUUsersTest, EqualRangeNoUpdateDoesNotChangeLru) {
140 UserCache cache(4);
141
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"});
146
147 auto [begin, end] = cache.equal_range_no_update<NameTag, std::string>("John");
148 int count = 0;
149 for (auto it = begin; it != end; ++it) {
150 ++count;
151 }
152 EXPECT_EQ(count, 2);
153
154 cache.emplace(User{5, "eve@test.com", "Eve"});
155
156 EXPECT_FALSE((cache.contains<IdTag>(1))); // evicted
157 EXPECT_TRUE((cache.contains<IdTag>(2))); // remains
158 EXPECT_TRUE((cache.contains<IdTag>(3))); // remains
159 EXPECT_TRUE((cache.contains<IdTag>(4))); // remains
160 EXPECT_TRUE((cache.contains<IdTag>(5))); // added
161}
162
163UTEST_F(LRUUsersTest, EqualRangeWorksWithEmptyRange) {
164 UserCache cache(3);
165 cache.emplace(User{1, "alice@test.com", "Alice"});
166
167 auto [begin, end] = cache.equal_range<NameTag, std::string>("Nonexistent");
168 EXPECT_EQ(begin, end);
169}
170
171UTEST_F(LRUUsersTest, EqualRangeNoUpdateWorksWithEmptyRange) {
172 UserCache cache(3);
173 cache.emplace(User{1, "alice@test.com", "Alice"});
174
175 auto [begin, end] = cache.equal_range_no_update<NameTag, std::string>("Nonexistent");
176 EXPECT_EQ(begin, end);
177}
178
179class ProductsTest : public ::testing::Test {
180protected:
181 struct SkuTag {};
182 struct NameTag {};
183
184 struct Product {
185 std::string sku;
186 std::string name;
187 double price;
188
189 bool operator==(const Product& other) const {
190 return sku == other.sku && name == other.name && price == other.price;
191 }
192 };
193
194 using ProductCache = multi_index_lru::Container<
195 Product,
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>>>>;
203};
204
205UTEST_F(ProductsTest, BasicProductOperations) {
206 ProductCache cache(2);
207
208 cache.emplace(Product{"A1", "Laptop", 999.99});
209 cache.emplace(Product{"A2", "Mouse", 29.99});
210
211 auto laptop = cache.find<SkuTag, std::string>("A1");
212 EXPECT_NE(laptop, cache.end<SkuTag>());
213 EXPECT_EQ(laptop->name, "Laptop");
214}
215
216UTEST_F(ProductsTest, ProductEviction) {
217 ProductCache cache(2);
218
219 cache.emplace(Product{"A1", "Laptop", 999.99});
220 cache.emplace(Product{"A2", "Mouse", 29.99});
221
222 // A1 was used, so A2 should be ousted when adding A3
223 cache.find<SkuTag>("A1");
224 cache.emplace(Product{"A3", "Keyboard", 79.99});
225
226 EXPECT_TRUE((cache.contains<SkuTag, std::string>("A1"))); // used
227 EXPECT_TRUE((cache.contains<SkuTag, std::string>("A3"))); // new
228 EXPECT_FALSE((cache.contains<SkuTag, std::string>("A2"))); // ousted
229
230 EXPECT_NE(cache.find<NameTag>("Keyboard"), cache.end<NameTag>());
231 EXPECT_EQ(cache.find<NameTag>("Mouse"), cache.end<NameTag>());
232}
233
234TEST(Snippet, SimpleUsage) {
235 struct MyValueT {
236 std::string key;
237 int val;
238 };
239
240 struct MyTag {};
241
242 MyValueT my_value{"some_key", 1};
243 /// [Usage]
244 using MyLruCache = multi_index_lru::Container<
245 MyValueT,
246 boost::multi_index::indexed_by<boost::multi_index::hashed_unique<
247 boost::multi_index::tag<MyTag>,
248 boost::multi_index::member<MyValueT, std::string, &MyValueT::key>>>>;
249
250 MyLruCache cache(1000); // Capacity of 1000 items
251 cache.insert(my_value);
252 auto it = cache.find<MyTag>("some_key");
253 EXPECT_NE(it, cache.end<MyTag>());
254 /// [Usage]
255}
256
257} // namespace
258
259USERVER_NAMESPACE_END