#include "hash_table_base.h"
#include "util/test/test.h"
#include <algorithm>
#include <vector>
class Int {
public:
explicit Int(int x) : x_(x) { creation_counter++; }
Int(const Int& other) : x_(other.x_) { creation_counter++; }
~Int() { destruction_counter++; }
int& x() { return x_; }
const int& x() const { return x_; }
size_t hash() const { return static_cast<size_t>(x_); }
static void ResetCounters() {
creation_counter = 0;
destruction_counter = 0;
}
int x_;
static size_t creation_counter;
static size_t destruction_counter;
};
size_t Int::creation_counter;
size_t Int::destruction_counter;
struct TestHashNode {
Int* int_ptr;
bool is_null() const { return !int_ptr; }
bool is_tombstone() const { return int_ptr == &kTombstone; }
bool is_valid() const { return !is_null() && !is_tombstone(); }
size_t hash_value() const { return int_ptr->hash(); }
static Int kTombstone;
};
Int TestHashNode::kTombstone(-1);
class TestHashTable : public HashTableBase<TestHashNode> {
public:
using BaseType = HashTableBase<TestHashNode>;
using Node = BaseType::Node;
static_assert(std::is_same<Node, TestHashNode>::value,
"HashTableBase<>::Node is not defined properly!");
BaseType& asBaseType() { return *this; }
const BaseType& asBaseType() const { return *this; }
TestHashTable() = default;
TestHashTable(const TestHashTable& other) : BaseType(other) {
for (Node& node : ValidNodesRange()) {
node.int_ptr = new Int(*node.int_ptr);
}
}
TestHashTable& operator=(const TestHashTable& other) {
if (this != &other) {
this->~TestHashTable();
new (this) TestHashTable(other);
}
return *this;
}
TestHashTable(TestHashTable&& other) noexcept : BaseType(std::move(other)) {}
TestHashTable& operator=(TestHashTable&& other) noexcept {
if (this != &other) {
this->~TestHashTable();
new (this) TestHashTable(std::move(other));
}
return *this;
}
~TestHashTable() {
for (Node& node : ValidNodesRange())
delete node.int_ptr;
}
bool contains(int x) const {
size_t hash = static_cast<size_t>(x);
Node* node = NodeLookup(
hash, [&](const Node* node) { return node->int_ptr->x() == x; });
return node->is_valid();
}
bool insert(int x) {
size_t hash = static_cast<size_t>(x);
Node* node = NodeLookup(
hash, [&](const Node* node) { return node->int_ptr->x() == x; });
if (node->is_valid())
return false;
bool was_tombstone = node->is_tombstone();
node->int_ptr = new Int(x);
UpdateAfterInsert(was_tombstone);
return true;
}
bool erase(int x) {
size_t hash = static_cast<size_t>(x);
Node* node = NodeLookup(
hash, [&](const Node* node) { return node->int_ptr->x() == x; });
if (!node->is_valid())
return false;
delete node->int_ptr;
node->int_ptr = &TestHashNode::kTombstone;
UpdateAfterRemoval();
return true;
}
void clear() {
for (Node& node : ValidNodesRange())
delete node.int_ptr;
NodeClear();
}
struct const_iterator : public BaseType::NodeIterator {
const int& operator*() const {
return (this->BaseType::NodeIterator::operator*()).int_ptr->x();
}
const int* operator->() const { return &(this->operator*()); }
};
const_iterator begin() const { return {BaseType::NodeBegin()}; }
const_iterator end() const { return {BaseType::NodeEnd()}; }
};
TEST(HashTableBaseTest, Construction) {
Int::ResetCounters();
{
TestHashTable table;
EXPECT_TRUE(table.empty());
EXPECT_EQ(table.size(), 0u);
EXPECT_EQ(table.begin(), table.end());
}
EXPECT_EQ(Int::creation_counter, 0u);
EXPECT_EQ(Int::destruction_counter, 0u);
}
TEST(HashTableBaseTest, InsertionsAndLookups) {
Int::ResetCounters();
{
TestHashTable table;
table.insert(1);
table.insert(5);
table.insert(7);
EXPECT_FALSE(table.empty());
EXPECT_EQ(table.size(), 3u);
EXPECT_NE(table.begin(), table.end());
EXPECT_EQ(Int::creation_counter, 3u);
EXPECT_EQ(Int::destruction_counter, 0u);
EXPECT_FALSE(table.contains(0));
EXPECT_TRUE(table.contains(1));
EXPECT_FALSE(table.contains(2));
EXPECT_FALSE(table.contains(3));
EXPECT_TRUE(table.contains(5));
EXPECT_FALSE(table.contains(6));
EXPECT_TRUE(table.contains(7));
EXPECT_FALSE(table.contains(8));
}
EXPECT_EQ(Int::creation_counter, 3u);
EXPECT_EQ(Int::destruction_counter, 3u);
}
TEST(HashTableBaseTest, CopyAssignment) {
Int::ResetCounters();
{
TestHashTable table;
table.insert(1);
table.insert(5);
table.insert(7);
EXPECT_FALSE(table.empty());
EXPECT_EQ(table.size(), 3u);
TestHashTable table2;
EXPECT_TRUE(table2.empty());
table2 = table;
EXPECT_FALSE(table2.empty());
EXPECT_EQ(table2.size(), 3u);
EXPECT_FALSE(table.empty());
EXPECT_EQ(table.size(), 3u);
EXPECT_EQ(Int::creation_counter, 6u);
EXPECT_EQ(Int::destruction_counter, 0u);
EXPECT_FALSE(table.contains(0));
EXPECT_TRUE(table.contains(1));
EXPECT_FALSE(table.contains(2));
EXPECT_FALSE(table.contains(3));
EXPECT_TRUE(table.contains(5));
EXPECT_FALSE(table.contains(6));
EXPECT_TRUE(table.contains(7));
EXPECT_FALSE(table.contains(8));
EXPECT_FALSE(table2.contains(0));
EXPECT_TRUE(table2.contains(1));
EXPECT_FALSE(table2.contains(2));
EXPECT_FALSE(table2.contains(3));
EXPECT_TRUE(table2.contains(5));
EXPECT_FALSE(table2.contains(6));
EXPECT_TRUE(table2.contains(7));
EXPECT_FALSE(table2.contains(8));
}
EXPECT_EQ(Int::creation_counter, 6u);
EXPECT_EQ(Int::destruction_counter, 6u);
}
TEST(HashTableBaseTest, MoveAssignment) {
Int::ResetCounters();
{
TestHashTable table;
table.insert(1);
table.insert(5);
table.insert(7);
EXPECT_FALSE(table.empty());
EXPECT_EQ(table.size(), 3u);
TestHashTable table2;
EXPECT_TRUE(table2.empty());
table2 = std::move(table);
EXPECT_FALSE(table2.empty());
EXPECT_EQ(table2.size(), 3u);
EXPECT_TRUE(table.empty());
EXPECT_EQ(table.size(), 0u);
EXPECT_EQ(Int::creation_counter, 3u);
EXPECT_EQ(Int::destruction_counter, 0u);
EXPECT_FALSE(table2.contains(0));
EXPECT_TRUE(table2.contains(1));
EXPECT_FALSE(table2.contains(2));
EXPECT_FALSE(table2.contains(3));
EXPECT_TRUE(table2.contains(5));
EXPECT_FALSE(table2.contains(6));
EXPECT_TRUE(table2.contains(7));
EXPECT_FALSE(table2.contains(8));
}
EXPECT_EQ(Int::creation_counter, 3u);
EXPECT_EQ(Int::destruction_counter, 3u);
}
TEST(HashTableBaseTest, Clear) {
Int::ResetCounters();
{
TestHashTable table;
table.insert(1);
table.insert(5);
table.insert(7);
EXPECT_FALSE(table.empty());
EXPECT_EQ(table.size(), 3u);
table.clear();
EXPECT_TRUE(table.empty());
EXPECT_EQ(Int::creation_counter, 3u);
EXPECT_EQ(Int::destruction_counter, 3u);
}
EXPECT_EQ(Int::creation_counter, 3u);
EXPECT_EQ(Int::destruction_counter, 3u);
}
TEST(HashTableBaseTest, Iteration) {
TestHashTable table;
table.insert(1);
table.insert(5);
table.insert(7);
EXPECT_FALSE(table.empty());
EXPECT_EQ(table.size(), 3u);
std::vector<int> values;
for (const int& x : table)
values.push_back(x);
std::sort(values.begin(), values.end());
EXPECT_EQ(values.size(), 3u);
EXPECT_EQ(values[0], 1);
EXPECT_EQ(values[1], 5);
EXPECT_EQ(values[2], 7);
}
TEST(HashTableBaseTest, Erase) {
TestHashTable table;
for (int i = 0; i < 16; ++i) {
table.insert(i);
table.erase(i);
EXPECT_EQ(table.size(), 0u);
}
}