280 lines
8.3 KiB
C
280 lines
8.3 KiB
C
/*
|
|
* test_str_intern.c — RED phase tests for foundation/str_intern.
|
|
*/
|
|
#include "test_framework.h"
|
|
#include "../src/foundation/str_intern.h"
|
|
|
|
TEST(intern_create_free) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
ASSERT_NOT_NULL(pool);
|
|
ASSERT_EQ(cbm_intern_count(pool), 0);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_basic) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *s1 = cbm_intern(pool, "hello");
|
|
ASSERT_NOT_NULL(s1);
|
|
ASSERT_STR_EQ(s1, "hello");
|
|
ASSERT_EQ(cbm_intern_count(pool), 1);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_dedup) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *s1 = cbm_intern(pool, "hello");
|
|
const char *s2 = cbm_intern(pool, "hello");
|
|
/* Must return same pointer */
|
|
ASSERT_EQ((uintptr_t)s1, (uintptr_t)s2);
|
|
ASSERT_EQ(cbm_intern_count(pool), 1); /* still 1 */
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_different_strings) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *s1 = cbm_intern(pool, "hello");
|
|
const char *s2 = cbm_intern(pool, "world");
|
|
ASSERT_NEQ((uintptr_t)s1, (uintptr_t)s2);
|
|
ASSERT_STR_EQ(s1, "hello");
|
|
ASSERT_STR_EQ(s2, "world");
|
|
ASSERT_EQ(cbm_intern_count(pool), 2);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_n_with_length) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *s1 = cbm_intern_n(pool, "hello world", 5);
|
|
ASSERT_STR_EQ(s1, "hello");
|
|
/* Should dedup with full "hello" */
|
|
const char *s2 = cbm_intern(pool, "hello");
|
|
ASSERT_EQ((uintptr_t)s1, (uintptr_t)s2);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_empty_string) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *s = cbm_intern(pool, "");
|
|
ASSERT_NOT_NULL(s);
|
|
ASSERT_STR_EQ(s, "");
|
|
ASSERT_EQ(cbm_intern_count(pool), 1);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_many_strings) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
char buf[64];
|
|
for (int i = 0; i < 1000; i++) {
|
|
snprintf(buf, sizeof(buf), "string_%04d", i);
|
|
const char *s = cbm_intern(pool, buf);
|
|
ASSERT_NOT_NULL(s);
|
|
}
|
|
ASSERT_EQ(cbm_intern_count(pool), 1000);
|
|
|
|
/* Verify dedup — intern same strings again */
|
|
for (int i = 0; i < 1000; i++) {
|
|
snprintf(buf, sizeof(buf), "string_%04d", i);
|
|
cbm_intern(pool, buf);
|
|
}
|
|
ASSERT_EQ(cbm_intern_count(pool), 1000); /* still 1000 */
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_bytes) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
cbm_intern(pool, "abc"); /* 3 bytes + NUL */
|
|
cbm_intern(pool, "defgh"); /* 5 bytes + NUL */
|
|
/* bytes should be at least 8 (3+5 for the content) */
|
|
ASSERT_GTE(cbm_intern_bytes(pool), 8);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_survives_stack_buffer) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *interned;
|
|
{
|
|
char buf[32];
|
|
strcpy(buf, "stack_string");
|
|
interned = cbm_intern(pool, buf);
|
|
}
|
|
/* buf is out of scope, but interned should still be valid */
|
|
ASSERT_STR_EQ(interned, "stack_string");
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
/* ── Edge case tests ───────────────────────────────────────────── */
|
|
|
|
TEST(intern_null_returns_null) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *s = cbm_intern(pool, NULL);
|
|
ASSERT_NULL(s);
|
|
ASSERT_EQ(cbm_intern_count(pool), 0);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_n_zero_len) {
|
|
/* intern_n with len=0 should produce an empty string */
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *s = cbm_intern_n(pool, "anything", 0);
|
|
ASSERT_NOT_NULL(s);
|
|
ASSERT_STR_EQ(s, "");
|
|
ASSERT_EQ(cbm_intern_count(pool), 1);
|
|
/* Should dedup with intern("") */
|
|
const char *s2 = cbm_intern(pool, "");
|
|
ASSERT_EQ((uintptr_t)s, (uintptr_t)s2);
|
|
ASSERT_EQ(cbm_intern_count(pool), 1);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_very_long_string) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
/* 2000 char string */
|
|
char buf[2001];
|
|
memset(buf, 'A', 2000);
|
|
buf[2000] = '\0';
|
|
const char *s1 = cbm_intern(pool, buf);
|
|
ASSERT_NOT_NULL(s1);
|
|
ASSERT_EQ(strlen(s1), 2000);
|
|
ASSERT_STR_EQ(s1, buf);
|
|
/* Dedup works for long strings too */
|
|
const char *s2 = cbm_intern(pool, buf);
|
|
ASSERT_EQ((uintptr_t)s1, (uintptr_t)s2);
|
|
ASSERT_EQ(cbm_intern_count(pool), 1);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_same_prefix_different_lengths) {
|
|
/* intern_n of same buffer with different lengths produces different strings */
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *src = "abcdefgh";
|
|
const char *s3 = cbm_intern_n(pool, src, 3); /* "abc" */
|
|
const char *s5 = cbm_intern_n(pool, src, 5); /* "abcde" */
|
|
const char *s8 = cbm_intern_n(pool, src, 8); /* "abcdefgh" */
|
|
ASSERT_STR_EQ(s3, "abc");
|
|
ASSERT_STR_EQ(s5, "abcde");
|
|
ASSERT_STR_EQ(s8, "abcdefgh");
|
|
/* All different pointers */
|
|
ASSERT_NEQ((uintptr_t)s3, (uintptr_t)s5);
|
|
ASSERT_NEQ((uintptr_t)s5, (uintptr_t)s8);
|
|
ASSERT_NEQ((uintptr_t)s3, (uintptr_t)s8);
|
|
ASSERT_EQ(cbm_intern_count(pool), 3);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_pointer_stability) {
|
|
/* Intern a string, then intern 10000 more, original pointer must survive */
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
const char *original = cbm_intern(pool, "sentinel_value");
|
|
ASSERT_NOT_NULL(original);
|
|
|
|
/* Force many hash table resizes */
|
|
char buf[64];
|
|
for (int i = 0; i < 10000; i++) {
|
|
snprintf(buf, sizeof(buf), "filler_%06d", i);
|
|
cbm_intern(pool, buf);
|
|
}
|
|
ASSERT_EQ(cbm_intern_count(pool), 10001);
|
|
|
|
/* Original pointer must still be valid and unchanged */
|
|
ASSERT_STR_EQ(original, "sentinel_value");
|
|
/* Re-interning must return the exact same pointer */
|
|
const char *again = cbm_intern(pool, "sentinel_value");
|
|
ASSERT_EQ((uintptr_t)original, (uintptr_t)again);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_two_pools_independent) {
|
|
/* Two pools must return different pointers for the same string */
|
|
CBMInternPool *p1 = cbm_intern_create();
|
|
CBMInternPool *p2 = cbm_intern_create();
|
|
const char *s1 = cbm_intern(p1, "shared");
|
|
const char *s2 = cbm_intern(p2, "shared");
|
|
ASSERT_STR_EQ(s1, "shared");
|
|
ASSERT_STR_EQ(s2, "shared");
|
|
/* Different pools own different copies — different pointers */
|
|
ASSERT_NEQ((uintptr_t)s1, (uintptr_t)s2);
|
|
cbm_intern_free(p1);
|
|
cbm_intern_free(p2);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_free_empty_pool) {
|
|
/* Free a pool with no strings interned — should not crash */
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
ASSERT_EQ(cbm_intern_count(pool), 0);
|
|
ASSERT_EQ(cbm_intern_bytes(pool), 0);
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_bytes_tracking) {
|
|
CBMInternPool *pool = cbm_intern_create();
|
|
ASSERT_EQ(cbm_intern_bytes(pool), 0);
|
|
|
|
cbm_intern(pool, "abc"); /* 3 bytes */
|
|
ASSERT_EQ(cbm_intern_bytes(pool), 3);
|
|
|
|
cbm_intern(pool, "defgh"); /* 5 bytes */
|
|
ASSERT_EQ(cbm_intern_bytes(pool), 8);
|
|
|
|
/* Duplicate should not add bytes */
|
|
cbm_intern(pool, "abc");
|
|
ASSERT_EQ(cbm_intern_bytes(pool), 8);
|
|
|
|
cbm_intern(pool, ""); /* 0 bytes */
|
|
ASSERT_EQ(cbm_intern_bytes(pool), 8);
|
|
|
|
cbm_intern(pool, "x"); /* 1 byte */
|
|
ASSERT_EQ(cbm_intern_bytes(pool), 9);
|
|
|
|
cbm_intern_free(pool);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_free_null_pool) {
|
|
/* Free NULL pool — should not crash */
|
|
cbm_intern_free(NULL);
|
|
PASS();
|
|
}
|
|
|
|
TEST(intern_count_null_pool) {
|
|
ASSERT_EQ(cbm_intern_count(NULL), 0);
|
|
PASS();
|
|
}
|
|
|
|
SUITE(str_intern) {
|
|
RUN_TEST(intern_create_free);
|
|
RUN_TEST(intern_basic);
|
|
RUN_TEST(intern_dedup);
|
|
RUN_TEST(intern_different_strings);
|
|
RUN_TEST(intern_n_with_length);
|
|
RUN_TEST(intern_empty_string);
|
|
RUN_TEST(intern_many_strings);
|
|
RUN_TEST(intern_bytes);
|
|
RUN_TEST(intern_survives_stack_buffer);
|
|
/* Edge cases */
|
|
RUN_TEST(intern_null_returns_null);
|
|
RUN_TEST(intern_n_zero_len);
|
|
RUN_TEST(intern_very_long_string);
|
|
RUN_TEST(intern_same_prefix_different_lengths);
|
|
RUN_TEST(intern_pointer_stability);
|
|
RUN_TEST(intern_two_pools_independent);
|
|
RUN_TEST(intern_free_empty_pool);
|
|
RUN_TEST(intern_bytes_tracking);
|
|
RUN_TEST(intern_free_null_pool);
|
|
RUN_TEST(intern_count_null_pool);
|
|
}
|