/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* 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 <folly/container/EvictingCacheMap.h>
#include <set>
#include <folly/portability/GTest.h>
using namespace folly;
TEST(EvictingCacheMap, SanityTest) {
EvictingCacheMap<int, int> map(0);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
EXPECT_FALSE(map.exists(1));
map.set(1, 1);
EXPECT_EQ(1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_EQ(1, map.get(1));
EXPECT_TRUE(map.exists(1));
map.set(1, 2);
EXPECT_EQ(1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_EQ(2, map.get(1));
EXPECT_TRUE(map.exists(1));
map.erase(1);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
EXPECT_FALSE(map.exists(1));
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
EXPECT_FALSE(map.exists(1));
map.set(1, 1);
EXPECT_EQ(1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_EQ(1, map.get(1));
EXPECT_TRUE(map.exists(1));
map.set(1, 2);
EXPECT_EQ(1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_EQ(2, map.get(1));
EXPECT_TRUE(map.exists(1));
EXPECT_FALSE(map.exists(2));
map.set(2, 1);
EXPECT_TRUE(map.exists(2));
EXPECT_EQ(2, map.size());
EXPECT_FALSE(map.empty());
EXPECT_EQ(1, map.get(2));
map.set(2, 2);
EXPECT_EQ(2, map.size());
EXPECT_FALSE(map.empty());
EXPECT_EQ(2, map.get(2));
EXPECT_TRUE(map.exists(2));
map.erase(2);
EXPECT_EQ(1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_FALSE(map.exists(2));
map.erase(1);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
EXPECT_FALSE(map.exists(1));
}
TEST(EvictingCacheMap, PruneTest) {
EvictingCacheMap<int, int> map(0);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
map.prune(1000000);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
map.prune(100);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
map.prune(99);
EXPECT_EQ(1, map.size());
EXPECT_FALSE(map.empty());
for (int i = 0; i < 99; i++) {
EXPECT_FALSE(map.exists(i));
}
EXPECT_TRUE(map.exists(99));
EXPECT_EQ(99, map.get(99));
map.prune(100);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
map.prune(90);
EXPECT_EQ(10, map.size());
EXPECT_FALSE(map.empty());
for (int i = 0; i < 90; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 90; i < 100; i++) {
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
}
TEST(EvictingCacheMap, PruneHookTest) {
EvictingCacheMap<int, int> map(0);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
int sum = 0;
auto pruneCb = [&](int&& k, int&& v) {
EXPECT_EQ(k, v);
sum += k;
};
EXPECT_FALSE(map.getPruneHook());
map.setPruneHook(pruneCb);
EXPECT_TRUE(map.getPruneHook());
{
int v = 42;
map.getPruneHook()(42, std::move(v));
}
EXPECT_EQ(42, sum);
sum = 0;
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
map.prune(1000000);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
EXPECT_EQ((99 * 100) / 2, sum);
sum = 0;
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
map.prune(100);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
EXPECT_EQ((99 * 100) / 2, sum);
sum = 0;
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
map.prune(99);
EXPECT_EQ(1, map.size());
EXPECT_FALSE(map.empty());
for (int i = 0; i < 99; i++) {
EXPECT_FALSE(map.exists(i));
}
EXPECT_TRUE(map.exists(99));
EXPECT_EQ(99, map.get(99));
EXPECT_EQ((98 * 99) / 2, sum);
sum = 0;
map.prune(100);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
EXPECT_EQ(99, sum);
sum = 0;
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
map.prune(90);
EXPECT_EQ(10, map.size());
EXPECT_FALSE(map.empty());
for (int i = 0; i < 90; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 90; i < 100; i++) {
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
EXPECT_EQ((89 * 90) / 2, sum);
sum = 0;
// Erase does not call prune hook (NOTE: possible source of usage bugs)
map.erase(99);
EXPECT_EQ(0, sum);
// But can provide your own hook
map.erase(98, pruneCb);
EXPECT_EQ(98, sum);
sum = 0;
// And with iterator
auto it1 = map.find(96);
auto it2 = map.find(97);
auto it3 = map.erase(it2, pruneCb);
EXPECT_EQ(it1, it3);
EXPECT_EQ(97, sum);
sum = 0;
// Destructor does not call prune hook (NOTE: possibly source of usage bugs)
map.~EvictingCacheMap<int, int>();
// Re-enter clean state
new (&map) EvictingCacheMap<int, int>(0);
EXPECT_EQ(0, sum);
}
TEST(EvictingCacheMap, SetMaxSize) {
EvictingCacheMap<int, int> map(100, 20);
for (int i = 0; i < 90; i++) {
map.set(i, i);
EXPECT_TRUE(map.exists(i));
}
EXPECT_EQ(90, map.size());
map.setMaxSize(50);
EXPECT_EQ(map.size(), 50);
for (int i = 0; i < 90; i++) {
map.set(i, i);
EXPECT_TRUE(map.exists(i));
}
EXPECT_EQ(40, map.size());
map.setMaxSize(0);
EXPECT_EQ(40, map.size());
map.setMaxSize(10);
EXPECT_EQ(10, map.size());
}
TEST(EvictingCacheMap, SetClearSize) {
EvictingCacheMap<int, int> map(100, 20);
for (int i = 0; i < 90; i++) {
map.set(i, i);
EXPECT_TRUE(map.exists(i));
}
EXPECT_EQ(90, map.size());
map.setClearSize(40);
map.setMaxSize(50);
EXPECT_EQ(map.size(), 50);
for (int i = 0; i < 90; i++) {
map.set(i, i);
EXPECT_TRUE(map.exists(i));
}
EXPECT_EQ(20, map.size());
map.setMaxSize(0);
EXPECT_EQ(20, map.size());
map.setMaxSize(10);
EXPECT_EQ(0, map.size());
}
TEST(EvictingCacheMap, DestructorInvocationTest) {
struct SumInt {
SumInt(int val_, int* ref_) : val(val_), ref(ref_) {}
~SumInt() { *ref += val; }
SumInt(SumInt const&) = delete;
SumInt& operator=(SumInt const&) = delete;
SumInt(SumInt&& other) : val(std::exchange(other.val, 0)), ref(other.ref) {}
SumInt& operator=(SumInt&& other) {
std::swap(val, other.val);
std::swap(ref, other.ref);
return *this;
}
int val;
int* ref;
};
int sum;
EvictingCacheMap<int, SumInt> map(0);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 0; i < 100; i++) {
map.set(i, SumInt(i, &sum));
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i).val);
}
sum = 0;
map.prune(1000000);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
EXPECT_EQ((99 * 100) / 2, sum);
for (int i = 0; i < 100; i++) {
map.set(i, SumInt(i, &sum));
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i).val);
}
sum = 0;
map.prune(100);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
EXPECT_EQ((99 * 100) / 2, sum);
for (int i = 0; i < 100; i++) {
map.set(i, SumInt(i, &sum));
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i).val);
}
sum = 0;
map.prune(99);
EXPECT_EQ(1, map.size());
EXPECT_FALSE(map.empty());
for (int i = 0; i < 99; i++) {
EXPECT_FALSE(map.exists(i));
}
EXPECT_TRUE(map.exists(99));
EXPECT_EQ(99, map.get(99).val);
EXPECT_EQ((98 * 99) / 2, sum);
sum = 0;
map.prune(100);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
EXPECT_EQ(99, sum);
for (int i = 0; i < 100; i++) {
map.set(i, SumInt(i, &sum));
EXPECT_EQ(i + 1, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i).val);
}
sum = 0;
map.prune(90);
EXPECT_EQ(10, map.size());
EXPECT_FALSE(map.empty());
for (int i = 0; i < 90; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 90; i < 100; i++) {
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i).val);
}
EXPECT_EQ((89 * 90) / 2, sum);
sum = 0;
for (int i = 0; i < 90; i++) {
auto pair = map.insert(i, SumInt(i + 1, &sum));
EXPECT_EQ(i + 1, pair.first->second.val);
EXPECT_TRUE(pair.second);
EXPECT_TRUE(map.exists(i));
}
EXPECT_EQ(0, sum);
for (int i = 90; i < 100; i++) {
auto pair = map.insert(i, SumInt(i + 1, &sum));
EXPECT_EQ(i, pair.first->second.val);
EXPECT_FALSE(pair.second);
EXPECT_TRUE(map.exists(i));
}
EXPECT_EQ((10 * 191) / 2, sum);
sum = 0;
map.prune(100);
EXPECT_EQ((90 * 91) / 2 + (10 * 189) / 2, sum);
sum = 0;
map.set(3, SumInt(3, &sum));
map.set(2, SumInt(2, &sum));
map.set(1, SumInt(1, &sum));
EXPECT_EQ(0, sum);
EXPECT_EQ(2, map.erase(map.find(1))->second.val);
EXPECT_EQ(1, sum);
EXPECT_EQ(map.end(), map.erase(map.findWithoutPromotion(3)));
EXPECT_EQ(4, sum);
map.prune(1);
EXPECT_EQ(6, sum);
}
TEST(EvictingCacheMap, LruSanityTest) {
EvictingCacheMap<int, int> map(10);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_GE(10, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
EXPECT_EQ(10, map.size());
EXPECT_FALSE(map.empty());
for (int i = 0; i < 90; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 90; i < 100; i++) {
EXPECT_TRUE(map.exists(i));
}
}
TEST(EvictingCacheMap, LruPromotionTest) {
EvictingCacheMap<int, int> map(10);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_GE(10, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
for (int j = 0; j < std::min(i + 1, 9); j++) {
EXPECT_TRUE(map.exists(j));
EXPECT_EQ(j, map.get(j));
}
}
EXPECT_EQ(10, map.size());
EXPECT_FALSE(map.empty());
for (int i = 0; i < 9; i++) {
EXPECT_TRUE(map.exists(i));
}
EXPECT_TRUE(map.exists(99));
for (int i = 10; i < 99; i++) {
EXPECT_FALSE(map.exists(i));
}
}
TEST(EvictingCacheMap, LruNoPromotionTest) {
EvictingCacheMap<int, int> map(10);
EXPECT_EQ(0, map.size());
EXPECT_TRUE(map.empty());
for (int i = 0; i < 100; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 0; i < 100; i++) {
map.set(i, i);
EXPECT_GE(10, map.size());
EXPECT_FALSE(map.empty());
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
for (int j = 0; j < std::min(i + 1, 9); j++) {
if (map.exists(j)) {
EXPECT_EQ(j, map.getWithoutPromotion(j));
}
}
}
EXPECT_EQ(10, map.size());
EXPECT_FALSE(map.empty());
for (int i = 0; i < 90; i++) {
EXPECT_FALSE(map.exists(i));
}
for (int i = 90; i < 100; i++) {
EXPECT_TRUE(map.exists(i));
}
}
TEST(EvictingCacheMap, IteratorSanityTest) {
const int nItems = 1000;
EvictingCacheMap<int, int> map(nItems);
EXPECT_TRUE(map.begin() == map.end());
for (int i = 0; i < nItems; i++) {
EXPECT_FALSE(map.exists(i));
map.set(i, i * 2);
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i * 2, map.get(i));
}
std::set<int> seen;
for (auto& it : map) {
EXPECT_EQ(0, seen.count(it.first));
seen.insert(it.first);
EXPECT_EQ(it.first * 2, it.second);
}
EXPECT_EQ(nItems, seen.size());
}
TEST(EvictingCacheMap, FindTest) {
const int nItems = 1000;
EvictingCacheMap<int, int> map(nItems);
for (int i = 0; i < nItems; i++) {
map.set(i * 2, i * 2);
EXPECT_TRUE(map.exists(i * 2));
EXPECT_EQ(i * 2, map.get(i * 2));
}
for (int i = 0; i < nItems * 2; i++) {
if (i % 2 == 0) {
auto it = map.find(i);
EXPECT_FALSE(it == map.end());
EXPECT_EQ(i, it->first);
EXPECT_EQ(i, it->second);
} else {
EXPECT_TRUE(map.find(i) == map.end());
}
}
for (int i = nItems * 2 - 1; i >= 0; i--) {
if (i % 2 == 0) {
auto it = map.find(i);
EXPECT_FALSE(it == map.end());
EXPECT_EQ(i, it->first);
EXPECT_EQ(i, it->second);
} else {
EXPECT_TRUE(map.find(i) == map.end());
}
}
EXPECT_EQ(0, map.begin()->first);
}
TEST(EvictingCacheMap, FindWithoutPromotionTest) {
const int nItems = 1000;
EvictingCacheMap<int, int> map(nItems);
for (int i = 0; i < nItems; i++) {
map.set(i * 2, i * 2);
EXPECT_TRUE(map.exists(i * 2));
EXPECT_EQ(i * 2, map.get(i * 2));
}
for (int i = nItems * 2 - 1; i >= 0; i--) {
if (i % 2 == 0) {
auto it = map.findWithoutPromotion(i);
EXPECT_FALSE(it == map.end());
EXPECT_EQ(i, it->first);
EXPECT_EQ(i, it->second);
} else {
EXPECT_TRUE(map.findWithoutPromotion(i) == map.end());
}
}
EXPECT_EQ((nItems - 1) * 2, map.begin()->first);
}
TEST(EvictingCacheMap, IteratorOrderingTest) {
const int nItems = 1000;
EvictingCacheMap<int, int> map(nItems);
for (int i = 0; i < nItems; i++) {
map.set(i, i);
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
int expected = nItems - 1;
for (auto it = map.begin(); it != map.end(); ++it) {
EXPECT_EQ(expected, it->first);
expected--;
}
expected = 0;
for (auto it = map.rbegin(); it != map.rend(); ++it) {
EXPECT_EQ(expected, it->first);
expected++;
}
{
auto it = map.end();
expected = 0;
EXPECT_TRUE(it != map.begin());
do {
--it;
EXPECT_EQ(expected, it->first);
expected++;
} while (it != map.begin());
EXPECT_EQ(nItems, expected);
}
{
auto it = map.rend();
expected = nItems - 1;
do {
--it;
EXPECT_EQ(expected, it->first);
expected--;
} while (it != map.rbegin());
EXPECT_EQ(-1, expected);
}
}
TEST(EvictingCacheMap, MoveTest) {
const int nItems = 1000;
EvictingCacheMap<int, int> map(nItems);
for (int i = 0; i < nItems; i++) {
map.set(i, i);
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
}
// Move to empty
EvictingCacheMap<int, int> map2 = std::move(map);
EXPECT_TRUE(map.empty());
for (int i = 0; i < nItems; i++) {
EXPECT_TRUE(map2.exists(i));
EXPECT_EQ(i, map2.get(i));
}
// Move to non-empty
EvictingCacheMap<int, int> map3(1);
map3.set(1, 1);
EXPECT_EQ(1, map3.size());
map3 = std::move(map2);
EXPECT_TRUE(map2.empty());
EXPECT_EQ(nItems, map3.size());
}
TEST(EvictingCacheMap, CustomKeyEqual) {
const int nItems = 100;
struct Eq {
bool operator()(const int& a, const int& b) const {
return (a % mod) == (b % mod);
}
int mod;
};
struct Hash {
size_t operator()(const int& a) const { return std::hash<int>()(a % mod); }
int mod;
};
EvictingCacheMap<int, int, Hash, Eq> map(
nItems, 1 /* clearSize */, Hash{nItems}, Eq{nItems});
for (int i = 0; i < nItems; i++) {
map.set(i, i);
EXPECT_TRUE(map.exists(i));
EXPECT_EQ(i, map.get(i));
EXPECT_TRUE(map.exists(i + nItems));
EXPECT_EQ(i, map.get(i + nItems));
}
}
TEST(EvictingCacheMap, InvalidHashPartlyUsable) {
// Some uses of EvictingCacheMap only use constructor+destructor in a header
// file where the hasher is invalid. (Only fully defined in cpp file.)
struct BadHash {};
struct BadEq {};
EvictingCacheMap<int, int, BadHash, BadEq> map{42};
// Also move ctor and operator
EvictingCacheMap<int, int, BadHash, BadEq> map2{std::move(map)};
map = std::move(map2);
}
TEST(EvictingCacheMap, IteratorConversion) {
using type = EvictingCacheMap<int, int>;
using i = type::iterator;
using ci = type::const_iterator;
using ri = type::reverse_iterator;
using cri = type::const_reverse_iterator;
EXPECT_TRUE((std::is_convertible<i, i>::value));
EXPECT_TRUE((std::is_convertible<i, ci>::value));
EXPECT_FALSE((std::is_convertible<ci, i>::value));
EXPECT_TRUE((std::is_convertible<ci, ci>::value));
EXPECT_TRUE((std::is_convertible<ri, ri>::value));
EXPECT_TRUE((std::is_convertible<ri, cri>::value));
EXPECT_FALSE((std::is_convertible<cri, ri>::value));
EXPECT_TRUE((std::is_convertible<cri, cri>::value));
}
TEST(EvictingCacheMap, HeterogeneousAccess) {
constexpr std::array pieces{
std::pair{"one"_sp, 1},
std::pair{"two"_sp, 2},
std::pair{"three"_sp, 3},
};
constexpr std::array charstars{
std::pair{"four", 4},
std::pair{"five", 5},
std::pair{"six", 6},
std::pair{"seven", 7},
};
EvictingCacheMap<std::string, int> map(0);
for (auto&& [key, value] : pieces) {
auto [_, inserted] = map.insert(key, value);
EXPECT_TRUE(inserted);
}
for (auto&& [key, value] : charstars) {
map.set(key, value);
}
for (auto&& [key, value] : pieces) {
auto exists = map.exists(key);
EXPECT_TRUE(exists);
auto iter = map.find(key);
EXPECT_TRUE(iter != map.end());
EXPECT_EQ(iter->second, value);
iter = map.findWithoutPromotion(key);
EXPECT_TRUE(iter != map.end());
EXPECT_EQ(iter->second, value);
}
for (auto&& [key, value] : charstars) {
auto result = map.get(key);
EXPECT_EQ(result, value);
result = map.getWithoutPromotion(key);
EXPECT_EQ(result, value);
}
for (auto&& [key, _] : pieces) {
auto erased = map.erase(key);
EXPECT_TRUE(erased);
erased = map.erase(key);
EXPECT_FALSE(erased);
}
for (auto&& [key, _] : charstars) {
map.erase(map.findWithoutPromotion(key));
}
EXPECT_TRUE(map.empty());
}
TEST(EvictingCacheMap, ApproximateEntryMemUsage) {
// Entry (without weight) should be
// * two pointers for LRU list
// * sizeof(key) and sizeof(value)
// * roughly 1.5 pointers for F14 index
// And sizeof(std::unique_ptr<char[]>) should usually be size of raw pointer
EXPECT_EQ(sizeof(std::unique_ptr<char[]>), sizeof(char*));
EXPECT_LE(
(EvictingCacheMap<uint64_t, std::unique_ptr<char[]>>::
kApproximateEntryMemUsage),
48U);
}
TEST(EvictingCacheMap, PiecewiseConstructTest) {
EvictingCacheMap<uint64_t, std::string> map(1);
uint64_t i = 1;
auto [iter, inserted] = map.try_emplace(i, "test");
EXPECT_TRUE(inserted);
EXPECT_EQ(iter->second, "test");
}