// 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; }