blob: b970d0d6a8f2b564bc5bbd83747a87657464aad6 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "paimon/common/utils/hash_map.h"
#include <cstdint>
#include <limits>
#include <map>
#include <memory>
#include <random>
#include "gtest/gtest.h"
#include "paimon/memory/memory_pool.h"
#include "paimon/testing/utils/testharness.h"
namespace paimon::test {
namespace {
using Map = HashMap<uint64_t, int64_t>;
} // namespace
TEST(HashMapTest, TestGrowthUpdatesAndIteration) {
ASSERT_OK_AND_ASSIGN(std::unique_ptr<Map> map, Map::Create(1, GetDefaultPool()));
ASSERT_EQ(map->Size(), 0);
ASSERT_FALSE(map->Find(0));
const uint64_t initial_memory = map->MemoryUsage();
std::map<uint64_t, int64_t> expected;
for (uint64_t key = 0; key < 300; ++key) {
ASSERT_OK(map->FindAndInsert(key, key + 10));
expected[key] = key + 10;
}
ASSERT_GT(map->MemoryUsage(), initial_memory);
const uint64_t memory_after_growth = map->MemoryUsage();
for (uint64_t key = 0; key < 300; ++key) {
const int64_t value = -static_cast<int64_t>(key) - 1;
ASSERT_OK(map->FindAndInsert(key, value));
expected[key] = value;
ASSERT_EQ(map->Find(key), value);
}
ASSERT_EQ(map->Size(), expected.size());
ASSERT_EQ(map->MemoryUsage(), memory_after_growth);
ASSERT_FALSE(map->Find(300));
std::map<uint64_t, int64_t> visited;
map->ForEach(
[&](uint64_t key, int64_t value) { ASSERT_TRUE(visited.emplace(key, value).second); });
ASSERT_EQ(visited, expected);
}
TEST(HashMapTest, TestBucketCollisionsAndKeyBitPatterns) {
ASSERT_OK_AND_ASSIGN(std::unique_ptr<Map> map, Map::Create(1, GetDefaultPool()));
// Distinct hashes with identical low bits start in the same bucket in these blocks.
for (uint64_t key = 0; key < 200; ++key) {
ASSERT_OK(map->FindAndInsert(key << 32, key * 2));
}
const uint64_t max_key = std::numeric_limits<uint64_t>::max();
ASSERT_OK(map->FindAndInsert(max_key, std::numeric_limits<int64_t>::min()));
for (uint64_t key = 0; key < 200; ++key) {
ASSERT_EQ(map->Find(key << 32), static_cast<int64_t>(key * 2));
}
ASSERT_EQ(map->Find(max_key), std::numeric_limits<int64_t>::min());
ASSERT_OK(map->FindAndInsert(0, std::numeric_limits<int64_t>::max()));
ASSERT_EQ(map->Find(0), std::numeric_limits<int64_t>::max());
ASSERT_EQ(map->Size(), 201);
ASSERT_FALSE(map->Find(uint64_t{201} << 32));
}
TEST(HashMapTest, TestDeterministicUpdatesMatchReference) {
ASSERT_OK_AND_ASSIGN(std::unique_ptr<Map> map, Map::Create(4, GetDefaultPool()));
std::map<uint64_t, int64_t> expected;
std::mt19937_64 random(42);
for (int32_t iteration = 0; iteration < 5000; ++iteration) {
const uint64_t key = random() % 1000;
const int64_t value = iteration - 2500;
ASSERT_OK(map->FindAndInsert(key, value));
expected[key] = value;
}
ASSERT_EQ(map->Size(), expected.size());
for (const auto& [key, value] : expected) {
ASSERT_EQ(map->Find(key), value);
}
}
TEST(HashMapTest, TestClearAndPoolLifetime) {
std::shared_ptr<MemoryPool> pool = GetMemoryPool();
std::weak_ptr<MemoryPool> weak_pool = pool;
ASSERT_OK_AND_ASSIGN(std::unique_ptr<Map> map, Map::Create(2, pool));
for (uint64_t key = 0; key < 100; ++key) {
ASSERT_OK(map->FindAndInsert(key, key));
}
ASSERT_EQ(map->MemoryUsage(), pool->CurrentUsage());
map->Clear();
ASSERT_EQ(map->Size(), 0);
ASSERT_EQ(map->MemoryUsage(), 0);
ASSERT_EQ(pool->CurrentUsage(), 0);
ASSERT_FALSE(map->Find(1));
uint64_t visited = 0;
map->ForEach([&](uint64_t, int64_t) { ++visited; });
ASSERT_EQ(visited, 0);
ASSERT_OK(map->FindAndInsert(7, 9));
ASSERT_EQ(map->Find(7), 9);
pool.reset();
ASSERT_FALSE(weak_pool.expired());
map.reset();
ASSERT_TRUE(weak_pool.expired());
}
TEST(HashMapTest, TestInvalidArguments) {
ASSERT_NOK_WITH_MSG(Map::Create(1, nullptr), "requires a pool");
ASSERT_NOK_WITH_MSG(Map::Create(0, GetDefaultPool()), "positive capacity");
ASSERT_NOK(Map::Create(std::numeric_limits<uint64_t>::max(), GetDefaultPool()));
ASSERT_NOK(Map::Create(std::numeric_limits<int64_t>::max() / 2, GetDefaultPool()));
}
} // namespace paimon::test