From 95dba7e478008f246060b304b33482ff328ad140 Mon Sep 17 00:00:00 2001 From: Kyler Date: Fri, 28 Nov 2025 20:07:32 -0700 Subject: [PATCH] Added Hash Table Tests --- SLS_C/include/tests/tests.h | 1 + SLS_C/tests/hash_table_tests.c | 383 +++++++++++++++++++++++++++++++++ SLS_C/tests/tests.c | 2 + 3 files changed, 386 insertions(+) create mode 100644 SLS_C/tests/hash_table_tests.c diff --git a/SLS_C/include/tests/tests.h b/SLS_C/include/tests/tests.h index 33b9a7d..2d89011 100644 --- a/SLS_C/include/tests/tests.h +++ b/SLS_C/include/tests/tests.h @@ -39,6 +39,7 @@ typedef struct { TestsReport run_string_tests(); TestsReport run_lexer_tests(); +TestsReport run_hash_table_tests(); TestsReport run_extra_tests(); #endif // SLS_TESTS_H diff --git a/SLS_C/tests/hash_table_tests.c b/SLS_C/tests/hash_table_tests.c new file mode 100644 index 0000000..0ea1935 --- /dev/null +++ b/SLS_C/tests/hash_table_tests.c @@ -0,0 +1,383 @@ +// Kyler Olsen +// YREA SLS +// Hash Table Tests +// November 2025 + +#include +#include +#include +#include + +#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; +} diff --git a/SLS_C/tests/tests.c b/SLS_C/tests/tests.c index 218f349..08d5f12 100644 --- a/SLS_C/tests/tests.c +++ b/SLS_C/tests/tests.c @@ -85,6 +85,8 @@ int main(void) { test_report(run_string_tests(), &counts); printf(" ========== Lexer Tests ==========\n"); test_report(run_lexer_tests(), &counts); + printf(" ========== Hash Table Tests ==========\n"); + test_report(run_hash_table_tests(), &counts); printf(" ========== Extra Tests ==========\n"); test_report(run_extra_tests(), &counts);