384 lines
13 KiB
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;
|
|
}
|