Change-Id: I4f5dbc664a621cdba3d928f2f358a73a07b71aa1 Reviewed-on: https://review.haiku-os.org/c/haiku/+/10671 Tested-by: Commit checker robot <[email protected]> Reviewed-by: waddlesplash <[email protected]>
327 lines
8.7 KiB
C++
327 lines
8.7 KiB
C++
/*
|
|
* Copyright 2020-2026, Haiku, Inc. All rights reserved.
|
|
* Distributed under the terms of the MIT License.
|
|
*/
|
|
|
|
|
|
#include <TestShell.h>
|
|
#include <TestSuiteAddon.h>
|
|
#include <cppunit/extensions/HelperMacros.h>
|
|
|
|
#include <os/support/ObjectList.h>
|
|
#include <shared/AutoDeleter.h>
|
|
#include <util/OpenHashTable.h>
|
|
|
|
|
|
namespace {
|
|
|
|
class Entry {
|
|
public:
|
|
Entry(uint32_t value)
|
|
:
|
|
fValue(value),
|
|
fNext(NULL)
|
|
{
|
|
}
|
|
|
|
const uint32_t Value() const { return fValue; }
|
|
|
|
Entry* Next() const { return fNext; }
|
|
|
|
private:
|
|
uint32_t fValue;
|
|
Entry* fNext;
|
|
|
|
friend class EntryDefinition;
|
|
};
|
|
|
|
|
|
class EntryDefinition {
|
|
public:
|
|
typedef uint32_t KeyType;
|
|
typedef Entry ValueType;
|
|
|
|
size_t HashKey(const KeyType& key) const { return key; }
|
|
|
|
size_t Hash(Entry* entry) const { return entry->fValue; }
|
|
|
|
bool Compare(const KeyType& key, Entry* entry) const { return key == entry->fValue; }
|
|
|
|
Entry*& GetLink(Entry* entry) const { return entry->fNext; }
|
|
};
|
|
|
|
} // namespace
|
|
|
|
|
|
class OpenHashTableTest : public CppUnit::TestFixture {
|
|
CPPUNIT_TEST_SUITE(OpenHashTableTest);
|
|
CPPUNIT_TEST(Init_ZeroSize_ReturnsOk);
|
|
CPPUNIT_TEST(Clear_EmptiesTable);
|
|
CPPUNIT_TEST(Clear_ReturnElementsSet_ClearsAndReturnsEntries);
|
|
CPPUNIT_TEST(Insert_AutoExpandDisabled_InsertsEntriesWithoutResize);
|
|
CPPUNIT_TEST(Insert_DuplicateEntry_ReturnsError);
|
|
CPPUNIT_TEST(Insert_InsertsEntry_ReturnsOk);
|
|
CPPUNIT_TEST(InsertUnchecked_InsertsEntry_ReturnsOk);
|
|
CPPUNIT_TEST(IterateAndCount_IteratesEntries_ReturnsCorrectCount);
|
|
CPPUNIT_TEST(Lookup_ExistingKey_ReturnsEntry);
|
|
CPPUNIT_TEST(Remove_ExistingEntry_ReturnsOk);
|
|
CPPUNIT_TEST(Remove_NotPresent_ReturnsNull);
|
|
CPPUNIT_TEST(RemoveUnchecked_RemovesEntry_ReturnsOk);
|
|
CPPUNIT_TEST(Resize_InsertsManyEntries_ResizesSuccessfully);
|
|
CPPUNIT_TEST_SUITE_END();
|
|
|
|
public:
|
|
void Init_ZeroSize_ReturnsOk()
|
|
{
|
|
Entry entry(123);
|
|
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(0), B_OK);
|
|
}
|
|
|
|
void Clear_EmptiesTable()
|
|
{
|
|
const size_t kEntryCount = 3;
|
|
|
|
BObjectList<Entry, true> entries(20);
|
|
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(), B_OK);
|
|
|
|
for (uint32_t i = 0; i < kEntryCount; ++i) {
|
|
Entry* entry = new Entry(i);
|
|
entries.AddItem(entry);
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(entry), B_OK);
|
|
}
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.CountElements(), kEntryCount);
|
|
CPPUNIT_ASSERT(table.Lookup(2) != NULL);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.Clear(false), NULL);
|
|
CPPUNIT_ASSERT_EQUAL(table.CountElements(), 0);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(2), NULL);
|
|
CPPUNIT_ASSERT_EQUAL(table.GetIterator().HasNext(), false);
|
|
}
|
|
|
|
void Clear_ReturnElementsSet_ClearsAndReturnsEntries()
|
|
{
|
|
// Same as ClearTest(), except that Clear(true) is called, which tells
|
|
// the BOpenHashTable<T> to return a linked list of entries before clearing
|
|
// the table.
|
|
const size_t kEntryCount = 3;
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(), B_OK);
|
|
|
|
for (uint32_t i = 0; i < kEntryCount; ++i) {
|
|
Entry* entry = new Entry(i);
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(entry), B_OK);
|
|
}
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.CountElements(), kEntryCount);
|
|
CPPUNIT_ASSERT(table.Lookup(2) != NULL);
|
|
|
|
Entry* head = table.Clear(true);
|
|
CPPUNIT_ASSERT(head != NULL);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.CountElements(), 0);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(2), NULL);
|
|
CPPUNIT_ASSERT_EQUAL(table.GetIterator().HasNext(), false);
|
|
|
|
size_t items_returned = 0;
|
|
while (head != NULL) {
|
|
Entry* next = head->Next();
|
|
delete head;
|
|
head = next;
|
|
|
|
++items_returned;
|
|
}
|
|
|
|
CPPUNIT_ASSERT_EQUAL(items_returned, kEntryCount);
|
|
}
|
|
|
|
void Insert_AutoExpandDisabled_InsertsEntriesWithoutResize()
|
|
{
|
|
// Insert multiple items into a table with a fixed size of 1. This
|
|
// essentially turns this BOpenHashTable into a linked list, since resize
|
|
// will never occur.
|
|
Entry entry1(123);
|
|
Entry entry2(456);
|
|
|
|
BOpenHashTable<EntryDefinition, false> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(1), B_OK);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(&entry1), B_OK);
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(&entry2), B_OK);
|
|
CPPUNIT_ASSERT_EQUAL(table.CountElements(), 2);
|
|
}
|
|
|
|
void Insert_DuplicateEntry_ReturnsError()
|
|
{
|
|
Entry entry(123);
|
|
|
|
BOpenHashTable<EntryDefinition, true, true> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(0), B_OK);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(&entry), B_OK);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), &entry);
|
|
|
|
CPPUNIT_ASSERT_DEBUGGER(table.Insert(&entry));
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), &entry);
|
|
|
|
// The item can basically never be removed now since there is a cycle,
|
|
// but we'll break into the debugger on remove when that happens as well.
|
|
CPPUNIT_ASSERT_DEBUGGER(table.Remove(&entry));
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), &entry);
|
|
|
|
CPPUNIT_ASSERT_DEBUGGER(table.Remove(&entry));
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), &entry);
|
|
}
|
|
|
|
void Insert_InsertsEntry_ReturnsOk()
|
|
{
|
|
Entry entry(123);
|
|
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(), B_OK);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(&entry), B_OK);
|
|
}
|
|
|
|
void InsertUnchecked_InsertsEntry_ReturnsOk()
|
|
{
|
|
Entry entry(123);
|
|
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(), B_OK);
|
|
|
|
table.InsertUnchecked(&entry);
|
|
}
|
|
|
|
void IterateAndCount_IteratesEntries_ReturnsCorrectCount()
|
|
{
|
|
const size_t kEntryCount = 20;
|
|
|
|
BObjectList<Entry, true> entries(20);
|
|
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(kEntryCount * 2), B_OK);
|
|
|
|
for (uint32_t i = 0; i < kEntryCount; ++i) {
|
|
Entry* entry = new Entry(i);
|
|
entries.AddItem(entry);
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(entry), B_OK);
|
|
}
|
|
|
|
// Verify that the table contains the expected values.
|
|
uint64_t map = 0;
|
|
BOpenHashTable<EntryDefinition>::Iterator iterator = table.GetIterator();
|
|
while (iterator.HasNext()) {
|
|
Entry* entry = iterator.Next();
|
|
CPPUNIT_ASSERT_EQUAL(0, map & (1 << entry->Value()));
|
|
map |= (1 << entry->Value());
|
|
}
|
|
|
|
CPPUNIT_ASSERT_EQUAL(map, (1 << kEntryCount) - 1);
|
|
CPPUNIT_ASSERT_EQUAL(kEntryCount, table.CountElements());
|
|
}
|
|
|
|
void Lookup_ExistingKey_ReturnsEntry()
|
|
{
|
|
Entry entry(123);
|
|
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(0), B_OK);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(&entry), B_OK);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), &entry);
|
|
}
|
|
|
|
void Remove_ExistingEntry_ReturnsOk()
|
|
{
|
|
Entry entry(123);
|
|
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(0), B_OK);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(&entry), B_OK);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), &entry);
|
|
|
|
table.Remove(&entry);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), NULL);
|
|
}
|
|
|
|
void Remove_NotPresent_ReturnsNull()
|
|
{
|
|
Entry entry1(123);
|
|
Entry entry2(456);
|
|
Entry entry3(789);
|
|
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(), B_OK);
|
|
|
|
// Only add the first two entries.
|
|
table.Insert(&entry1);
|
|
table.Insert(&entry2);
|
|
|
|
// entry3 is not in the table, but we'll remove it anyway.
|
|
table.Remove(&entry3);
|
|
table.RemoveUnchecked(&entry3);
|
|
|
|
// The original two entries should still be there.
|
|
CPPUNIT_ASSERT_EQUAL(table.CountElements(), 2);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), &entry1);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(456), &entry2);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(789), NULL);
|
|
}
|
|
|
|
void RemoveUnchecked_RemovesEntry_ReturnsOk()
|
|
{
|
|
Entry entry(123);
|
|
|
|
BOpenHashTable<EntryDefinition> table;
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(0), B_OK);
|
|
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(&entry), B_OK);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), &entry);
|
|
|
|
table.RemoveUnchecked(&entry);
|
|
CPPUNIT_ASSERT_EQUAL(table.Lookup(123), NULL);
|
|
}
|
|
|
|
void Resize_InsertsManyEntries_ResizesSuccessfully()
|
|
{
|
|
// This is the same as IterateAndCountTest, except that the table will
|
|
// be resized mid insertion.
|
|
const size_t kEntryCount = 20;
|
|
BObjectList<Entry, true> entries(20);
|
|
BOpenHashTable<EntryDefinition> table;
|
|
|
|
// Start off with capacity for 8 elements. This will mean that the table
|
|
// will be resized in the loop below since we are inserting 20 elements.
|
|
CPPUNIT_ASSERT_EQUAL(table.Init(8), B_OK);
|
|
|
|
for (uint32_t i = 0; i < kEntryCount; ++i) {
|
|
Entry* entry = new Entry(i);
|
|
entries.AddItem(entry);
|
|
CPPUNIT_ASSERT_EQUAL(table.Insert(entry), B_OK);
|
|
}
|
|
|
|
// Verify that after resize the expected elements are present within
|
|
// the table.
|
|
uint64_t map = 0;
|
|
BOpenHashTable<EntryDefinition>::Iterator iterator = table.GetIterator();
|
|
while (iterator.HasNext()) {
|
|
Entry* entry = iterator.Next();
|
|
CPPUNIT_ASSERT_EQUAL(0, map & (1 << entry->Value()));
|
|
map |= (1 << entry->Value());
|
|
}
|
|
|
|
CPPUNIT_ASSERT_EQUAL(map, (1 << kEntryCount) - 1);
|
|
CPPUNIT_ASSERT_EQUAL(kEntryCount, table.CountElements());
|
|
}
|
|
};
|
|
|
|
|
|
CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(OpenHashTableTest, getTestSuiteName());
|