Files
hm.h/test.c
2026-10-29 00:01:50 +01:00

269 lines
7.1 KiB
C

#define HM_IMPLEMENTATION
#include "hm.h"
#include <stdio.h>
static int failures = 0;
#define CHECK(cond) \
do { \
if (!(cond)) { \
fprintf(stderr, "%s:%d: CHECK failed: %s\n", \
__FILE__, __LINE__, #cond); \
++failures; \
} \
} while (0)
// Forces every key into the same chain
static uint64_t const_hash(const void *buffer, size_t size)
{
(void)buffer;
(void)size;
return 0;
}
static void test_int_keys(void)
{
HashMap hm = hm_create(sizeof(int), sizeof(int));
const int n = 10000;
for (int i = 0; i < n; ++i) {
int v = i * 2;
CHECK(hm_put(&hm, &i, &v));
}
CHECK(hm_count(&hm) == (size_t)n);
for (int i = 0; i < n; ++i) {
int *v = hm_of(int, &hm, &i);
CHECK(v != NULL && *v == i * 2);
}
int missing = n;
CHECK(hm_get(&hm, &missing) == NULL);
for (int i = 0; i < n; i += 2) {
CHECK(hm_remove(&hm, &i));
}
CHECK(hm_count(&hm) == (size_t)n / 2);
for (int i = 0; i < n; ++i) {
CHECK((hm_get(&hm, &i) != NULL) == (i % 2 == 1));
}
hm_free(&hm);
}
static void test_string_value_resize_in_chain(void)
{
HashMap hm = hm_create_ex(0, 0, const_hash);
char big[4096];
memset(big, 'y', sizeof(big) - 1);
big[sizeof(big) - 1] = '\0';
// Chain: c -> b -> a
CHECK(hm_put(&hm, "a", "x"));
CHECK(hm_put(&hm, "b", "x"));
CHECK(hm_put(&hm, "c", "x"));
// Grow every bucket so realloc moves them, then shrink them back
CHECK(hm_put(&hm, "a", big));
CHECK(hm_put(&hm, "b", big));
CHECK(hm_put(&hm, "c", big));
CHECK(strcmp((char*)hm_get(&hm, "b"), big) == 0);
CHECK(hm_put(&hm, "b", "short"));
CHECK(strcmp((char*)hm_get(&hm, "b"), "short") == 0);
CHECK(hm_count(&hm) == 3);
// Removing neighbors walks the prev/next pointers fixed up above
CHECK(hm_remove(&hm, "a"));
CHECK(hm_remove(&hm, "c"));
CHECK(hm_remove(&hm, "b"));
CHECK(hm_count(&hm) == 0);
hm_free(&hm);
}
static void test_alignment(void)
{
// Variable length string keys followed by doubles
HashMap hm = hm_create(0, sizeof(double));
const char *keys[] = { "a", "ab", "abc", "abcd", "abcde", "abcdefg" };
for (size_t i = 0; i < sizeof(keys) / sizeof(keys[0]); ++i) {
double d = (double)i + 0.5;
CHECK(hm_put(&hm, keys[i], &d));
double *p = hm_of(double, &hm, keys[i]);
CHECK(p != NULL && (uintptr_t)p % alignof(double) == 0);
CHECK(p != NULL && *p == d);
}
hm_free(&hm);
// Odd-sized struct keys followed by struct values
typedef struct { int a, b, c; } Key;
typedef struct { double x; long long y; } Value;
hm = hm_create(sizeof(Key), sizeof(Value));
for (int i = 0; i < 100; ++i) {
Key k;
memset(&k, 0, sizeof(k));
k.a = i;
Value v = { (double)i, i };
CHECK(hm_put(&hm, &k, &v));
CHECK((uintptr_t)hm_get(&hm, &k) % alignof(Value) == 0);
CHECK((uintptr_t)hm_get(&hm, &k) % alignof(double) == 0);
CHECK(hm_of(Value, &hm, &k)->y == i);
}
HashMapIterator it = hm_iterate(&hm);
while (hm_next(&it) != NULL) {
CHECK((uintptr_t)hm_key(&it) % alignof(int) == 0);
}
hm_free(&hm);
}
static void test_zero_initialized(void)
{
HashMap hm = {0};
CHECK(hm_get(&hm, "key") == NULL);
CHECK(!hm_remove(&hm, "key"));
CHECK(hm_put(&hm, "key", "value"));
CHECK(strcmp((char*)hm_get(&hm, "key"), "value") == 0);
hm_free(&hm);
}
static void test_reuse_after_free(void)
{
HashMap hm = hm_create(sizeof(int), sizeof(int));
int k = 1, v = 2;
CHECK(hm_put(&hm, &k, &v));
hm_free(&hm);
CHECK(hm_count(&hm) == 0);
CHECK(hm_get(&hm, &k) == NULL);
// Configuration survives hm_free()
v = 3;
CHECK(hm_put(&hm, &k, &v));
CHECK(*hm_of(int, &hm, &k) == 3);
hm_free(&hm);
hm_free(&hm);
}
static void test_iteration(void)
{
HashMap hm = hm_create(sizeof(int), sizeof(int));
const int n = 1000;
static bool seen[1000];
HashMapIterator it = hm_iterate(&hm);
CHECK(hm_next(&it) == NULL);
for (int i = 0; i < n; ++i) {
CHECK(hm_put(&hm, &i, &i));
}
size_t visited = 0;
it = hm_iterate(&hm);
while (hm_next(&it) != NULL) {
int key = *(const int*)hm_key(&it);
CHECK(key >= 0 && key < n && !seen[key]);
CHECK(*(int*)hm_value(&it) == key);
seen[key] = true;
++visited;
}
CHECK(visited == (size_t)n);
CHECK(hm_key(&it) == NULL && hm_value(&it) == NULL);
hm_free(&hm);
}
static void test_update_does_not_rehash(void)
{
HashMap hm = hm_create(sizeof(int), sizeof(int));
const int n = (int)(HM_INITIAL_CAPACITY * HM_MAX_LOAD_FACTOR);
for (int i = 0; i < n; ++i) {
CHECK(hm_put(&hm, &i, &i));
}
CHECK(hm.capacity == HM_INITIAL_CAPACITY);
int k = 0, v = 42;
CHECK(hm_put(&hm, &k, &v));
CHECK(hm.capacity == HM_INITIAL_CAPACITY);
CHECK(*hm_of(int, &hm, &k) == 42);
hm_free(&hm);
}
static void test_custom_hash(void)
{
HashMap hm = hm_create_ex(0, sizeof(int), const_hash);
char key[16];
for (int i = 0; i < 500; ++i) {
snprintf(key, sizeof(key), "k%d", i);
CHECK(hm_put(&hm, key, &i));
}
for (int i = 0; i < 500; ++i) {
snprintf(key, sizeof(key), "k%d", i);
CHECK(*hm_of(int, &hm, key) == i);
}
hm_free(&hm);
hm = hm_create_ex(0, 0, NULL);
CHECK(hm.hash_func == HM_DEFAULT_HASH_FUNCTION);
hm_free(&hm);
}
static size_t last_hash_size;
// Records the size it receives
static uint64_t size_hash(const void *buffer, size_t size)
{
last_hash_size = size;
return hm__fnv1a(buffer, size);
}
static void test_hash_size(void)
{
// NULL terminated keys are hashed without their terminator
HashMap hm = hm_create_ex(0, 0, size_hash);
CHECK(hm_put(&hm, "abc", "v"));
CHECK(last_hash_size == 3);
CHECK(hm_get(&hm, "abc") != NULL);
CHECK(last_hash_size == 3);
CHECK(hm_put(&hm, "", "empty"));
CHECK(last_hash_size == 0);
CHECK(strcmp((char*)hm_get(&hm, ""), "empty") == 0);
CHECK(hm_remove(&hm, ""));
CHECK(last_hash_size == 0);
hm_free(&hm);
// Fixed-size keys are hashed with their full size
hm = hm_create_ex(sizeof(int), sizeof(int), size_hash);
int k = 7;
CHECK(hm_put(&hm, &k, &k));
CHECK(last_hash_size == sizeof(int));
hm_free(&hm);
}
int main(void)
{
test_int_keys();
test_string_value_resize_in_chain();
test_alignment();
test_zero_initialized();
test_reuse_after_free();
test_iteration();
test_update_does_not_rehash();
test_custom_hash();
test_hash_size();
if (failures > 0) {
fprintf(stderr, "%d check(s) failed\n", failures);
return 1;
}
printf("All tests passed\n");
return 0;
}