2 * Copyright (C) 1996-2023 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
10 #include "base/ClpMap.h"
11 #include "compat/cppunit.h"
12 #include "SquidConfig.h"
13 #include "unitTestMain.h"
17 class TestClpMap
: public CPPUNIT_NS::TestFixture
19 CPPUNIT_TEST_SUITE( TestClpMap
);
20 CPPUNIT_TEST( testMemoryCounter
);
21 CPPUNIT_TEST( testConstructor
);
22 CPPUNIT_TEST( testEntryCounter
);
23 CPPUNIT_TEST( testPutGetDelete
);
24 CPPUNIT_TEST( testMisses
);
25 CPPUNIT_TEST( testMemoryLimit
);
26 CPPUNIT_TEST( testTtlExpiration
);
27 CPPUNIT_TEST( testReplaceEntryWithShorterTtl
);
28 CPPUNIT_TEST( testZeroTtl
);
29 CPPUNIT_TEST( testNegativeTtl
);
30 CPPUNIT_TEST( testPurgeIsLru
);
31 CPPUNIT_TEST_SUITE_END();
34 void setUp() override
;
37 using Map
= ClpMap
<std::string
, int>;
39 void testMemoryCounter();
40 void testConstructor();
41 void testEntryCounter();
42 void testPutGetDelete();
44 void testMemoryLimit();
45 void testTtlExpiration();
46 void testReplaceEntryWithShorterTtl();
48 void testNegativeTtl();
49 void testPurgeIsLru();
51 /// Generate and insert the given number of entries into the given map. Each
52 /// entry is guaranteed to be inserted, but that insertion may purge other
53 /// entries, including entries previously added during the same method call.
54 void addSequenceOfEntriesToMap(Map
&, size_t count
, Map::mapped_type startWith
, Map::Ttl
);
56 /// add (more than) enough entries to make the map full
57 void fillMapWithEntries(Map
&);
59 /// generate and add an entry with a given value (and a matching key) to the
60 /// map using map-default TTL
61 void addOneEntry(Map
&, Map::mapped_type
);
63 /// generate and add an entry with a given value, a matching key, and a
64 /// given TTL to the map
65 void addOneEntry(Map
&, Map::mapped_type
, Map::Ttl
);
68 CPPUNIT_TEST_SUITE_REGISTRATION( TestClpMap
);
70 class SquidConfig Config
;
73 TestClpMap::addSequenceOfEntriesToMap(Map
&m
, size_t count
, const Map::mapped_type startWith
, const Map::Ttl ttl
)
75 for (auto j
= startWith
; count
; ++j
, --count
)
76 CPPUNIT_ASSERT(m
.add(std::to_string(j
), j
, ttl
));
80 TestClpMap::fillMapWithEntries(Map
&m
)
82 addSequenceOfEntriesToMap(m
, m
.memLimit() / sizeof(Map::mapped_type
), 0, 10);
86 TestClpMap::addOneEntry(Map
&m
, const Map::mapped_type value
)
88 const auto key
= std::to_string(value
);
89 CPPUNIT_ASSERT(m
.add(key
, value
));
90 CPPUNIT_ASSERT(m
.get(key
));
91 CPPUNIT_ASSERT_EQUAL(value
, *m
.get(key
));
95 TestClpMap::addOneEntry(Map
&m
, const Map::mapped_type value
, const Map::Ttl ttl
)
97 const auto key
= std::to_string(value
);
98 CPPUNIT_ASSERT(m
.add(key
, value
, ttl
));
99 CPPUNIT_ASSERT(m
.get(key
));
100 CPPUNIT_ASSERT_EQUAL(value
, *m
.get(key
));
106 squid_curtime
= time(nullptr);
110 TestClpMap::testPutGetDelete()
113 addSequenceOfEntriesToMap(m
, 10, 0, 10);
114 CPPUNIT_ASSERT(m
.get("1")); // we get something
115 CPPUNIT_ASSERT_EQUAL(1, *(m
.get("1"))); // we get what we put in
116 CPPUNIT_ASSERT(m
.get("9"));
117 CPPUNIT_ASSERT_EQUAL(9, *(m
.get("9")));
119 CPPUNIT_ASSERT(m
.get("1"));
120 CPPUNIT_ASSERT_EQUAL(99, *(m
.get("1")));
122 CPPUNIT_ASSERT(!m
.get("1")); // entry has been cleared
126 TestClpMap::testMisses()
129 fillMapWithEntries(m
);
130 const auto entriesBefore
= m
.entries();
131 CPPUNIT_ASSERT(!m
.get("not-there"));
133 CPPUNIT_ASSERT_EQUAL(entriesBefore
, m
.entries());
137 TestClpMap::testEntryCounter()
140 Map
m(10*1024*1024, 10);
141 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), m
.entries());
142 addSequenceOfEntriesToMap(m
, 10, 10, 10);
143 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(10), m
.entries());
145 CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(11), m
.entries());
149 addSequenceOfEntriesToMap(m
, 1000, 0, 10);
150 CPPUNIT_ASSERT(m
.entries() < 1000);
155 TestClpMap::testMemoryCounter()
157 CPPUNIT_ASSERT_EQUAL(sizeof(int), static_cast<size_t>(DefaultMemoryUsage(int{})));
158 CPPUNIT_ASSERT_EQUAL(sizeof(int32_t), static_cast<size_t>(DefaultMemoryUsage(int32_t{})));
159 CPPUNIT_ASSERT_EQUAL(sizeof(int64_t), static_cast<size_t>(DefaultMemoryUsage(int64_t{})));
160 CPPUNIT_ASSERT_EQUAL(sizeof(char), static_cast<size_t>(DefaultMemoryUsage(char{})));
161 using Str
= char[10];
162 CPPUNIT_ASSERT_EQUAL(sizeof(Str
), static_cast<size_t>(DefaultMemoryUsage(Str
{})));
166 TestClpMap::testConstructor()
169 CPPUNIT_ASSERT_EQUAL(uint64_t(0), nilA
.memLimit());
170 CPPUNIT_ASSERT_EQUAL(uint64_t(0), nilA
.freeMem());
171 CPPUNIT_ASSERT_EQUAL(uint64_t(0), nilA
.memoryUsed());
172 CPPUNIT_ASSERT_EQUAL(size_t(0), nilA
.entries());
174 const Map
nilB(0, 0);
175 CPPUNIT_ASSERT_EQUAL(uint64_t(0), nilB
.memLimit());
176 CPPUNIT_ASSERT_EQUAL(uint64_t(0), nilB
.freeMem());
177 CPPUNIT_ASSERT_EQUAL(uint64_t(0), nilB
.memoryUsed());
178 CPPUNIT_ASSERT_EQUAL(size_t(0), nilB
.entries());
181 CPPUNIT_ASSERT_EQUAL(uint64_t(1), emptyC
.memLimit());
182 CPPUNIT_ASSERT_EQUAL(uint64_t(1), emptyC
.freeMem());
183 CPPUNIT_ASSERT_EQUAL(uint64_t(0), emptyC
.memoryUsed());
184 CPPUNIT_ASSERT_EQUAL(size_t(0), emptyC
.entries());
186 const Map
emptyD(1024);
187 CPPUNIT_ASSERT_EQUAL(uint64_t(1024), emptyD
.memLimit());
188 CPPUNIT_ASSERT_EQUAL(uint64_t(1024), emptyD
.freeMem());
189 CPPUNIT_ASSERT_EQUAL(uint64_t(0), emptyD
.memoryUsed());
190 CPPUNIT_ASSERT_EQUAL(size_t(0), emptyD
.entries());
194 TestClpMap::testMemoryLimit()
196 const size_t initialCapacity
= 1024; // bytes
197 Map
m(initialCapacity
);
198 fillMapWithEntries(m
);
199 const auto entriesAtInitialCapacity
= m
.entries();
201 // check that all entries are removed if we prohibit storage of any entries
203 CPPUNIT_ASSERT_EQUAL(size_t(0), m
.entries());
205 // test whether the map can grow after the all-at-once purging above
206 const auto increasedCapacity
= initialCapacity
* 2;
207 m
.setMemLimit(increasedCapacity
);
208 fillMapWithEntries(m
);
209 CPPUNIT_ASSERT(m
.entries() > entriesAtInitialCapacity
);
211 // test that memory usage and entry count decrease when the map is shrinking
212 // but prevent endless loops no matter how broken ClpMap implementation is
213 auto iterationsLeft
= m
.entries();
214 CPPUNIT_ASSERT(0 < iterationsLeft
&& iterationsLeft
<= increasedCapacity
);
215 while (m
.entries()) {
216 // TODO: Check that we can still add a (smaller) entry here.
218 const auto memoryUsedBefore
= m
.memoryUsed();
219 const auto entriesBefore
= m
.entries();
221 const auto newMemoryLimit
= memoryUsedBefore
/2; // may become zero
222 m
.setMemLimit(newMemoryLimit
);
224 CPPUNIT_ASSERT(m
.memoryUsed() <= newMemoryLimit
);
225 CPPUNIT_ASSERT(m
.entries() < entriesBefore
);
227 // the assertion below may fail if ClpMap::entries() returns bogus numbers
228 CPPUNIT_ASSERT(iterationsLeft
> 0);
232 // test whether the map can grow after all that gradual purging above
233 m
.setMemLimit(increasedCapacity
);
234 fillMapWithEntries(m
);
235 CPPUNIT_ASSERT(m
.entries() > entriesAtInitialCapacity
);
239 TestClpMap::testTtlExpiration()
243 addOneEntry(m
, 0, 100);
245 CPPUNIT_ASSERT(m
.get("0")); // still fresh
246 squid_curtime
+= 100;
247 CPPUNIT_ASSERT(!m
.get("0")); // has expired
251 // same test, but using a map-specific TTL instead of entry-specific one
255 CPPUNIT_ASSERT(m
.get("0")); // still fresh
256 squid_curtime
+= 100;
257 CPPUNIT_ASSERT(!m
.get("0")); // has expired
261 // same test, but using both map-specific and entry-specific TTLs
263 addOneEntry(m
, 0, 100);
265 CPPUNIT_ASSERT(m
.get("0")); // still fresh
266 squid_curtime
+= 100;
267 CPPUNIT_ASSERT(!m
.get("0")); // has expired
272 TestClpMap::testReplaceEntryWithShorterTtl()
275 addOneEntry(m
, 0, 100);
276 addOneEntry(m
, 0, 10); // same (key, value) entry but with shorter TTL
278 CPPUNIT_ASSERT(!m
.get("0")); // has expired
280 // now the same sequence but with a time change between additions
281 addOneEntry(m
, 0, 100);
282 squid_curtime
+= 200;
283 addOneEntry(m
, 0, 10);
284 CPPUNIT_ASSERT(m
.get("0")); // still fresh due to new TTL
286 CPPUNIT_ASSERT(!m
.get("0")); // has expired
290 TestClpMap::testZeroTtl()
294 addOneEntry(m
, 0, 0);
296 CPPUNIT_ASSERT(!m
.get("0")); // expired, we get nothing
300 // same test, but using a map-specific TTL instead of entry-specific one
304 CPPUNIT_ASSERT(!m
.get("0")); // expired, we get nothing
308 // same test, but using both map-specific and entry-specific TTLs
310 addOneEntry(m
, 0, 0);
312 CPPUNIT_ASSERT(!m
.get("0")); // expired, we get nothing
317 TestClpMap::testNegativeTtl()
321 // we start with an ordinary-TTL entry to check that it will be purged below
322 addOneEntry(m
, 0, 10);
324 // check that negative-TTL entries are rejected
325 CPPUNIT_ASSERT(!m
.add("0", 0, -1));
327 // check that an attempt to add a negative-TTL entry purges the previously
328 // added ordinary-TTL entry
329 CPPUNIT_ASSERT(!m
.get("0"));
331 // check that the same entry can be re-added with a non-negative TTL
336 TestClpMap::testPurgeIsLru()
339 for (int j
= 0; j
< 10; ++j
)
341 // now overflow the map while keeping "0" the Least Recently Used
342 for (int j
= 100; j
< 1000; ++j
) {
344 CPPUNIT_ASSERT(m
.get("0"));
346 // these should have been aged out
347 CPPUNIT_ASSERT(!m
.get("1"));
348 CPPUNIT_ASSERT(!m
.get("2"));
349 CPPUNIT_ASSERT(!m
.get("3"));
350 CPPUNIT_ASSERT(!m
.get("4"));
352 fillMapWithEntries(m
);
353 CPPUNIT_ASSERT(!m
.get("0")); // removable when not recently used