#define _DEFAULT_SOURCE /* strsignal() is only declared under this with -std=c11 */ #include #include #include #include #include #include "malunal/container.h" #include "malunal/containers/vector.h" #include "malunal/allocators.h" /* ------------------------------------------------------------------ */ /* Minimal assertion + test-registration harness (no framework) */ /* ------------------------------------------------------------------ */ #define CHECK(cond, msg) \ do { \ if (!(cond)) { \ fprintf(stderr, " assertion failed: %s (%s:%d)\n", \ (msg), __FILE__, __LINE__); \ _exit(1); /* fail fast within the child: state is unreliable */ \ } \ } while (0) #define CHECK_EQ_SZ(actual, expected, msg) \ do { \ malunal_size_t _a = (malunal_size_t)(actual); \ malunal_size_t _e = (malunal_size_t)(expected); \ if (_a != _e) { \ fprintf(stderr, \ " assertion failed: %s (%s:%d) got=%llu want=%llu\n", \ (msg), __FILE__, __LINE__, \ (unsigned long long)_a, (unsigned long long)_e); \ _exit(1); \ } \ } while (0) #define CHECK_EQ_INT(actual, expected, msg) \ do { \ int _a = (int)(actual); \ int _e = (int)(expected); \ if (_a != _e) { \ fprintf(stderr, " assertion failed: %s (%s:%d) got=%d want=%d\n", \ (msg), __FILE__, __LINE__, _a, _e); \ _exit(1); \ } \ } while (0) typedef void (*test_fn_t)(void); typedef struct { const char* name; test_fn_t fn; } test_case_t; #define MAX_TESTS 64 static test_case_t g_tests[MAX_TESTS]; static int g_test_count = 0; static void register_test(const char* name, test_fn_t fn) { g_tests[g_test_count].name = name; g_tests[g_test_count].fn = fn; g_test_count++; } #define TEST(name) \ static void name(void); \ static void name##_register(void) __attribute__((constructor)); \ static void name##_register(void) { register_test(#name, name); } \ static void name(void) /* Runs a single test in a forked child so a crash can't take out the * rest of the suite. Returns 1 for pass, 0 for fail/crash. */ static int run_isolated(test_fn_t fn) { pid_t pid = fork(); if (pid < 0) { perror("fork"); return 0; } if (pid == 0) { /* child */ fn(); _exit(0); /* reached only if no CHECK failed */ } int status = 0; waitpid(pid, &status, 0); if (WIFEXITED(status) && WEXITSTATUS(status) == 0) return 1; if (WIFSIGNALED(status)) { int sig = WTERMSIG(status); fprintf(stderr, " CRASHED: terminated by signal %d (%s)\n", sig, strsignal(sig)); } return 0; } /* ------------------------------------------------------------------ */ /* Shared setup helper */ /* ------------------------------------------------------------------ */ /* Initializes a fresh vector-as-container with a libc-backed allocator. * `allocator_out` is filled in because callers need to keep the * allocator alive (by value) for the container's lifetime. */ static container_mptr_t setup_container( vector_t* vector, allocator_t* allocator_out, malunal_size_t stride, malunal_size_t capacity ) { *vector = vector_container(); *allocator_out = libc_allocator(); container_mptr_t container = (container_mptr_t)vector; container_exception_t rc = container_initialize( container, allocator_out, stride, capacity); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_initialize should succeed"); return container; } /* ------------------------------------------------------------------ */ /* vector_container() -- construction */ /* ------------------------------------------------------------------ */ TEST(construct_then_uninitialized_accessors_are_zero) { vector_t vector = vector_container(); container_iptr_t container = (container_iptr_t)&vector; malunal_size_t value = 123; /* sentinel so we know it was actually written */ container_exception_t rc = container_stride(container, &value); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_stride should succeed pre-initialize"); CHECK_EQ_SZ(value, 0, "stride should start at 0"); rc = container_count(container, &value); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_count should succeed pre-initialize"); CHECK_EQ_SZ(value, 0, "count should start at 0"); rc = container_capacity(container, &value); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_capacity should succeed pre-initialize"); CHECK_EQ_SZ(value, 0, "capacity should start at 0"); } TEST(vector_t_and_container_t_are_same_size) { CHECK_EQ_SZ(sizeof(vector_t), sizeof(container_t), "vector_t must match container_t size"); } /* ------------------------------------------------------------------ */ /* container_initialize / container_finalize */ /* ------------------------------------------------------------------ */ TEST(initialize_allocates_backing_storage) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); allocator_mptr_t allocator = &libc_alloc; malunal_size_t value = 0; container_mptr_t container = (container_mptr_t)&vector; container_exception_t error = container_initialize( container, allocator, sizeof(int), 4); CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to initialize"); error = container_stride(container, &value); CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to provide stride"); CHECK(value == sizeof(int), "Stride should match requested element size"); error = container_count(container, &value); CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to provide count"); CHECK(value == 0, "Count should be 0 right after initialize"); error = container_capacity(container, &value); CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to provide capacity"); CHECK(value == 4, "Capacity should match requested capacity"); error = container_finalize(container); CHECK(error == CONTAINER_ERROR_SUCCESS, "Container failed to finalize"); } TEST(allocator_accessor_returns_the_allocator_used) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = (container_mptr_t)&vector; container_initialize(container, &libc_alloc, sizeof(int), 4); allocator_mptr_t got = NULL; container_exception_t rc = container_allocator(container, &got); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_allocator should succeed"); CHECK(got == &libc_alloc, "container_allocator should return the allocator passed to initialize"); container_finalize(container); } /* ------------------------------------------------------------------ */ /* container_append + vector_get */ /* ------------------------------------------------------------------ */ TEST(append_then_get_round_trips_value) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int value = 42; container_exception_t rc = container_append(container, &value); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_append should succeed"); int out = 0; rc = vector_get(&vector, 0, &out); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "vector_get should succeed for freshly appended element"); CHECK_EQ_INT(out, 42, "vector_get should return the value that was appended"); container_finalize(container); } TEST(append_increments_count) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1, b = 2; container_append(container, &a); container_append(container, &b); malunal_size_t count = 0; container_count(container, &count); CHECK_EQ_SZ(count, 2, "count should track number of appends"); int out0 = 0, out1 = 0; vector_get(&vector, 0, &out0); vector_get(&vector, 1, &out1); CHECK_EQ_INT(out0, 1, "first appended element preserved"); CHECK_EQ_INT(out1, 2, "second appended element preserved"); container_finalize(container); } TEST(append_past_capacity_grows_and_preserves_data) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 2); int vals[5] = { 10, 20, 30, 40, 50 }; for (int i = 0; i < 5; i++) { container_exception_t rc = container_append(container, &vals[i]); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "append should succeed while growing"); } malunal_size_t capacity = 0, count = 0; container_capacity(container, &capacity); container_count(container, &count); CHECK(capacity >= 5, "capacity should have grown to fit 5 elements"); CHECK_EQ_SZ(count, 5, "count should be 5 after 5 appends"); for (int i = 0; i < 5; i++) { int out = 0; vector_get(&vector, i, &out); CHECK_EQ_INT(out, vals[i], "element should survive a growth reallocation"); } container_finalize(container); } /* ------------------------------------------------------------------ */ /* container_clear */ /* ------------------------------------------------------------------ */ TEST(clear_resets_count_but_keeps_capacity) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1, b = 2; container_append(container, &a); container_append(container, &b); container_exception_t rc = container_clear(container); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_clear should succeed"); malunal_size_t count = 99, capacity = 0; container_count(container, &count); container_capacity(container, &capacity); CHECK_EQ_SZ(count, 0, "clear should reset count to 0"); CHECK_EQ_SZ(capacity, 4, "clear should not release/shrink capacity"); container_finalize(container); } /* ------------------------------------------------------------------ */ /* vector_set */ /* ------------------------------------------------------------------ */ TEST(set_overwrites_existing_element) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1, b = 2, replacement = 99; container_append(container, &a); container_append(container, &b); container_exception_t rc = vector_set(&vector, 0, &replacement); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "vector_set should succeed for an in-range index"); int out = 0; vector_get(&vector, 0, &out); CHECK_EQ_INT(out, 99, "vector_set should overwrite the element at the given index"); container_finalize(container); } TEST(get_set_report_out_of_bounds_at_count) { /* Per container.h/vector.h, get/set should fail once the index * reaches or exceeds the number of *live* elements. Capacity may * still have room, but reading/writing an unset slot is out of * bounds. */ vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1; container_append(container, &a); /* count == 1, capacity == 4 */ int out = 0; container_exception_t rc = vector_get(&vector, 1, &out); CHECK_EQ_INT(rc, CONTAINER_ERROR_OUT_OF_BOUNDS, "get(index == count) should be out of bounds, not read uninitialized capacity"); container_finalize(container); } TEST(get_set_report_out_of_bounds_at_capacity) { /* index == capacity is one past the last valid slot. */ vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1; container_append(container, &a); int out = 0; container_exception_t rc = vector_get(&vector, 4, &out); CHECK_EQ_INT(rc, CONTAINER_ERROR_OUT_OF_BOUNDS, "get(index == capacity) should be out of bounds"); container_finalize(container); } /* ------------------------------------------------------------------ */ /* vector_index_of */ /* ------------------------------------------------------------------ */ TEST(index_of_finds_present_element) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 5, b = 6, c = 7; container_append(container, &a); container_append(container, &b); container_append(container, &c); malunal_size_t idx = (malunal_size_t)-1; int needle = 6; container_exception_t rc = vector_index_of(&vector, &needle, &idx); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "index_of should succeed when element is present"); CHECK_EQ_SZ(idx, 1, "index_of should report the correct index of the matching element"); container_finalize(container); } TEST(index_of_reports_missing_element) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 5; container_append(container, &a); malunal_size_t idx = 0; int needle = 999; vector_index_of(&vector, &needle, &idx); /* Documented contract: outidx should be set to a not-found sentinel * (-1) when the element isn't found. */ CHECK_EQ_SZ(idx, (malunal_size_t)-1, "index_of should report a not-found sentinel"); container_finalize(container); } TEST(index_of_actually_compares_against_the_search_element) { /* vector_index_of_def_impl's loop does * memcmp(buffer, buffer + stride, stride) * i.e. it compares each element to its *neighbor*, and never touches * the `element` argument at all. So it doesn't search for the given * value -- it finds the first position holding two equal adjacent * elements, whatever the caller was actually looking for. This test * makes that failure mode concrete and unambiguous: [1, 1, 2], * searching for 2, should report index 2 -- not index 0. */ vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1, b = 1, c = 2; container_append(container, &a); container_append(container, &b); container_append(container, &c); /* [1, 1, 2] */ malunal_size_t idx = 0; int needle = 2; vector_index_of(&vector, &needle, &idx); CHECK_EQ_SZ(idx, 2, "index_of should find the index of the *searched* value, not an adjacent duplicate pair"); container_finalize(container); } /* ------------------------------------------------------------------ */ /* vector_insert_at */ /* ------------------------------------------------------------------ */ TEST(insert_at_end_appends_value) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1, b = 2, c = 3; container_append(container, &a); container_append(container, &b); container_exception_t rc = vector_insert_at(&vector, 2, &c); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "insert_at(count) should succeed"); int out = 0; vector_get(&vector, 2, &out); CHECK_EQ_INT(out, 3, "inserted value should be readable back at the target index"); container_finalize(container); } TEST(insert_at_middle_shifts_later_elements) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 8); int a = 1, b = 3, mid = 2; container_append(container, &a); container_append(container, &b); /* [1, 3] */ container_exception_t rc = vector_insert_at(&vector, 1, &mid); /* -> [1, 2, 3] */ CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "insert_at(1) should succeed"); int out0 = 0, out1 = 0, out2 = 0; vector_get(&vector, 0, &out0); vector_get(&vector, 1, &out1); vector_get(&vector, 2, &out2); CHECK_EQ_INT(out0, 1, "element before insertion point unchanged"); CHECK_EQ_INT(out1, 2, "inserted element lands at requested index"); CHECK_EQ_INT(out2, 3, "element after insertion point shifted right"); container_finalize(container); } /* ------------------------------------------------------------------ */ /* vector_remove_at */ /* ------------------------------------------------------------------ */ TEST(remove_at_last_leaves_earlier_elements_untouched) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 8); int a = 1, b = 2, c = 3; container_append(container, &a); container_append(container, &b); container_append(container, &c); /* [1, 2, 3] */ container_exception_t rc = vector_remove_at(&vector, 2); /* remove the '3' */ CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "remove_at(last) should succeed"); malunal_size_t count = 0; container_count(container, &count); CHECK_EQ_SZ(count, 2, "count should drop by one after removal"); int out0 = 0, out1 = 0; vector_get(&vector, 0, &out0); vector_get(&vector, 1, &out1); CHECK_EQ_INT(out0, 1, "element 0 should be unaffected by removing the last element"); CHECK_EQ_INT(out1, 2, "element 1 should be unaffected by removing the last element"); container_finalize(container); } TEST(remove_at_middle_shifts_later_elements_left) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 8); int a = 1, b = 2, c = 3; container_append(container, &a); container_append(container, &b); container_append(container, &c); /* [1, 2, 3] */ container_exception_t rc = vector_remove_at(&vector, 0); /* -> [2, 3] */ CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "remove_at(0) should succeed"); int out0 = 0, out1 = 0; vector_get(&vector, 0, &out0); vector_get(&vector, 1, &out1); CHECK_EQ_INT(out0, 2, "elements after the removed index should shift left"); CHECK_EQ_INT(out1, 3, "elements after the removed index should shift left"); container_finalize(container); } TEST(remove_at_on_empty_vector_is_out_of_bounds) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); container_exception_t rc = vector_remove_at(&vector, 0); CHECK_EQ_INT(rc, CONTAINER_ERROR_OUT_OF_BOUNDS, "remove_at on an empty vector should be rejected as out of bounds"); container_finalize(container); } /* ------------------------------------------------------------------ */ /* container_remove */ /* ------------------------------------------------------------------ */ TEST(container_remove_removes_the_first_matching_element) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 8); int a = 10, b = 20, c = 30; container_append(container, &a); container_append(container, &b); container_append(container, &c); /* [10, 20, 30] */ container_exception_t rc = container_remove(container, &a); /* remove the leading '10' */ CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "container_remove should succeed for a present element"); malunal_size_t count = 0; container_count(container, &count); CHECK_EQ_SZ(count, 2, "count should drop by one after container_remove"); int out0 = 0; vector_get(&vector, 0, &out0); CHECK_EQ_INT(out0, 20, "removing the first element should shift the rest left"); container_finalize(container); } TEST(container_remove_of_absent_element_is_success) { /* container.h: "if an element does not exist in the container, the * most common response should be CONTAINER_ERROR_SUCCESS." */ vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1; container_append(container, &a); int missing = 404; container_exception_t rc = container_remove(container, &missing); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "removing an absent element should report success, per the documented contract"); container_finalize(container); } /* ------------------------------------------------------------------ */ /* container_contains */ /* ------------------------------------------------------------------ */ TEST(container_contains_finds_a_present_element) { vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1, b = 2; container_append(container, &a); container_append(container, &b); container_exception_t rc = container_contains(container, &b); CHECK_EQ_INT(rc, CONTAINER_ERROR_SUCCESS, "contains should succeed for a present element"); container_finalize(container); } TEST(container_contains_rejects_an_absent_element) { /* vector_contains_impl does `index > 0 ? SUCCESS : FAILURE`. But * vector_index_of's not-found sentinel is (malunal_size_t)-1, i.e. * SIZE_MAX, which is also > 0 -- so an absent element is reported as * *found*. (Combined with index_of's separate bug of comparing * adjacent elements instead of the search value, this vector has no * adjacent duplicates, so index_of legitimately reports "not * found" via the -1 sentinel here -- and contains still says yes.) */ vector_t vector = vector_container(); allocator_t libc_alloc = libc_allocator(); container_mptr_t container = setup_container(&vector, &libc_alloc, sizeof(int), 4); int a = 1, b = 2; container_append(container, &a); container_append(container, &b); /* [1, 2], no adjacent duplicates */ int missing = 999; container_exception_t rc = container_contains(container, &missing); CHECK_EQ_INT(rc, CONTAINER_ERROR_FAILURE, "contains should report failure for an absent element"); container_finalize(container); } /* ------------------------------------------------------------------ */ /* runner */ /* ------------------------------------------------------------------ */ int main(void) { int passed = 0; printf("running %d tests\n\n", g_test_count); for (int i = 0; i < g_test_count; i++) { printf("[ RUN ] %s\n", g_tests[i].name); int ok = run_isolated(g_tests[i].fn); printf("[%s] %s\n\n", ok ? " PASS " : " FAIL ", g_tests[i].name); passed += ok; } printf("---------------------------------------------\n"); printf("%d / %d tests passed\n", passed, g_test_count); return passed == g_test_count ? 0 : 1; }