YREA-SLS/SLS_C/tests/hash_table_tests.c

384 lines
13 KiB
C

// Kyler Olsen
// YREA SLS
// Hash Table Tests
// November 2025
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "sls/string.h"
#include "sls/hash_table.h"
#include "tests/tests.h"
static const size_t NUM_HASH_TABLE_TESTS = 12;
static TestResult pass_hash_table_test(SlsStr test_name) {
TestResult result = {.name = test_name, .status = TEST_NOT_IMPLEMENTED};
result.status = TEST_PASS;
return result;
}
static TestResult fail_hash_table_test(SlsStr test_name, SlsStr message) {
TestResult result = { .name = test_name, .status = TEST_NOT_IMPLEMENTED };
result.status = TEST_ERROR;
result.message = sls_str_cpy(message);
if (result.message.str == NULL) {
result.error = (SlsError){ SLS_STR("Out Of Memory Error."), 1 };
result.status = TEST_ERROR;
}
return result;
}
// Test init_hash_table and del_hash_table
static TestResult test_init_and_delete() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_init_and_delete"), SLS_STR("Initialization failed"));
if (ht->buckets_count < 10) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_init_and_delete"), SLS_STR("Bucket count less than minimum"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_init_and_delete"));
}
// Test basic put and get operations
static TestResult test_put_and_get() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Initialization failed"));
int value1 = 42;
int value2 = 84;
SlsStr key1 = SLS_STR("key1");
SlsStr key2 = SLS_STR("key2");
if (!hash_table_put(ht, key1, &value1)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Put operation failed"));
}
if (!hash_table_put(ht, key2, &value2)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Second put operation failed"));
}
int *retrieved1 = (int*)hash_table_get(ht, key1, NULL);
int *retrieved2 = (int*)hash_table_get(ht, key2, NULL);
if (!retrieved1 || *retrieved1 != 42) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Get operation failed for key1"));
}
if (!retrieved2 || *retrieved2 != 84) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_put_and_get"), SLS_STR("Get operation failed for key2"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_put_and_get"));
}
// Test get with non-existent key returns default
static TestResult test_get_nonexistent_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_get_nonexistent_key"), SLS_STR("Initialization failed"));
int default_value = -1;
SlsStr key = SLS_STR("nonexistent");
int *result = (int*)hash_table_get(ht, key, &default_value);
if (result != &default_value) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_get_nonexistent_key"), SLS_STR("Did not return default value"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_get_nonexistent_key"));
}
// Test updating existing key
static TestResult test_update_existing_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_update_existing_key"), SLS_STR("Initialization failed"));
int value1 = 100;
int value2 = 200;
SlsStr key = SLS_STR("update_key");
hash_table_put(ht, key, &value1);
hash_table_put(ht, key, &value2);
int *result = (int*)hash_table_get(ht, key, NULL);
if (!result || *result != 200) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_update_existing_key"), SLS_STR("Update failed"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_update_existing_key"));
}
// Test delete operation
static TestResult test_delete_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Initialization failed"));
int value = 42;
SlsStr key = SLS_STR("delete_key");
hash_table_put(ht, key, &value);
if (!hash_table_del(ht, key)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Delete returned FALSE"));
}
int default_val = -1;
int *result = (int*)hash_table_get(ht, key, &default_val);
if (result != &default_val) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_delete_key"), SLS_STR("Key still exists after delete"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_delete_key"));
}
// Test delete non-existent key returns FALSE
static TestResult test_delete_nonexistent_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_delete_nonexistent_key"), SLS_STR("Initialization failed"));
SlsStr key = SLS_STR("nonexistent");
if (hash_table_del(ht, key)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_delete_nonexistent_key"), SLS_STR("Delete returned TRUE for nonexistent key"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_delete_nonexistent_key"));
}
// Test collision handling (multiple keys in same bucket)
static TestResult test_collision_handling() {
HashTable *ht = init_hash_table(1); // Force collisions with single bucket
if (!ht) return fail_hash_table_test(SLS_STR("test_collision_handling"), SLS_STR("Initialization failed"));
int val1 = 1, val2 = 2, val3 = 3;
SlsStr key1 = SLS_STR("key1");
SlsStr key2 = SLS_STR("key2");
SlsStr key3 = SLS_STR("key3");
hash_table_put(ht, key1, &val1);
hash_table_put(ht, key2, &val2);
hash_table_put(ht, key3, &val3);
int *r1 = (int*)hash_table_get(ht, key1, NULL);
int *r2 = (int*)hash_table_get(ht, key2, NULL);
int *r3 = (int*)hash_table_get(ht, key3, NULL);
if (!r1 || *r1 != 1 || !r2 || *r2 != 2 || !r3 || *r3 != 3) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_collision_handling"), SLS_STR("Collision handling failed"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_collision_handling"));
}
// Test empty string key
static TestResult test_empty_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Initialization failed"));
int value = 99;
SlsStr key = SLS_STR("");
if (!hash_table_put(ht, key, &value)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Put with empty key failed"));
}
int *result = (int*)hash_table_get(ht, key, NULL);
if (!result || *result != 99) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_empty_key"), SLS_STR("Get with empty key failed"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_empty_key"));
}
// Test long key
static TestResult test_long_key() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Initialization failed"));
char long_key_str[256];
for (size_t i = 0; i < 255; i++) long_key_str[i] = 'A';
long_key_str[255] = '\0';
SlsStr key = sls_str_malloc(long_key_str, 255);
int value = 777;
if (!hash_table_put(ht, key, &value)) {
sls_str_free(&key);
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Put with long key failed"));
}
int *result = (int*)hash_table_get(ht, key, NULL);
if (!result || *result != 777) {
sls_str_free(&key);
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_long_key"), SLS_STR("Get with long key failed"));
}
sls_str_free(&key);
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_long_key"));
}
// Test multiple operations sequence
static TestResult test_multiple_operations() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Initialization failed"));
int vals[5] = {10, 20, 30, 40, 50};
SlsStr keys[5] = {SLS_STR("k1"), SLS_STR("k2"), SLS_STR("k3"), SLS_STR("k4"), SLS_STR("k5")};
// Insert all
for (int i = 0; i < 5; i++) {
if (!hash_table_put(ht, keys[i], &vals[i])) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Insert failed"));
}
}
// Delete some
hash_table_del(ht, keys[1]);
hash_table_del(ht, keys[3]);
// Verify remaining
int *r0 = (int*)hash_table_get(ht, keys[0], NULL);
int *r2 = (int*)hash_table_get(ht, keys[2], NULL);
int *r4 = (int*)hash_table_get(ht, keys[4], NULL);
if (!r0 || *r0 != 10 || !r2 || *r2 != 30 || !r4 || *r4 != 50) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Get after delete failed"));
}
// Verify deleted
int def = -1;
int *r1 = (int*)hash_table_get(ht, keys[1], &def);
int *r3 = (int*)hash_table_get(ht, keys[3], &def);
if (r1 != &def || r3 != &def) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_multiple_operations"), SLS_STR("Deleted keys still present"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_multiple_operations"));
}
// Test storing NULL pointers
static TestResult test_null_value() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("Initialization failed"));
SlsStr key = SLS_STR("null_key");
if (!hash_table_put(ht, key, NULL)) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("Put with NULL value failed"));
}
int default_val = -1;
void *result = hash_table_get(ht, key, &default_val);
// Should retrieve NULL, not default
if (result == &default_val) {
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_null_value"), SLS_STR("NULL value not stored correctly"));
}
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_null_value"));
}
// Test large number of entries
static TestResult test_large_capacity() {
HashTable *ht = init_hash_table(10);
if (!ht) return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Initialization failed"));
const size_t COUNT = 100;
int *values = malloc(sizeof(int) * COUNT);
SlsStr *keys = malloc(sizeof(SlsStr) * COUNT);
for (size_t i = 0; i < COUNT; i++) {
values[i] = (int)i;
char key_buf[32];
snprintf(key_buf, 32, "key_%zu", i);
keys[i] = sls_str_malloc(key_buf, strlen(key_buf));
if (!hash_table_put(ht, keys[i], &values[i])) {
for (size_t j = 0; j <= i; j++) sls_str_free(&keys[j]);
free(keys);
free(values);
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Put failed in bulk insert"));
}
}
// Verify all entries
for (size_t i = 0; i < COUNT; i++) {
int *result = (int*)hash_table_get(ht, keys[i], NULL);
if (!result || *result != (int)i) {
for (size_t j = 0; j < COUNT; j++) sls_str_free(&keys[j]);
free(keys);
free(values);
del_hash_table(ht);
return fail_hash_table_test(SLS_STR("test_large_capacity"), SLS_STR("Get failed in bulk verify"));
}
}
for (size_t i = 0; i < COUNT; i++) sls_str_free(&keys[i]);
free(keys);
free(values);
del_hash_table(ht);
return pass_hash_table_test(SLS_STR("test_large_capacity"));
}
// Run all hash table tests
TestsReport run_hash_table_tests() {
TestsReport report = {
.section = SLS_STR("hash_table_tests"),
.count = NUM_HASH_TABLE_TESTS,
.tests = malloc(sizeof(TestResult) * NUM_HASH_TABLE_TESTS)
};
size_t i = 0;
report.tests[i++] = test_init_and_delete();
report.tests[i++] = test_put_and_get();
report.tests[i++] = test_get_nonexistent_key();
report.tests[i++] = test_update_existing_key();
report.tests[i++] = test_delete_key();
report.tests[i++] = test_delete_nonexistent_key();
report.tests[i++] = test_collision_handling();
report.tests[i++] = test_empty_key();
report.tests[i++] = test_long_key();
report.tests[i++] = test_multiple_operations();
report.tests[i++] = test_null_value();
report.tests[i++] = test_large_capacity();
return report;
}