#include #include #include #include #include #include #include /* Use the same Unity include pattern as other controller tests */ #include #include "repl.h" #include "config.h" static void test_repl_init_zeroes_nstandbys(void) { replicator_t r; TEST_ASSERT_EQUAL_INT(0, repl_init(&r)); TEST_ASSERT_EQUAL_INT(0, r.nstandbys); repl_fini(&r); printf("test_repl_init_zeroes_nstandbys: PASS\n"); } static void test_repl_add_remove_standby(void) { replicator_t r; /* Use a dummy int as the endpoint pointer — repl.c only compares ep pointers */ int dummy_ep = 0; repl_init(&r); int idx = repl_add_standby(&r, (void*)&dummy_ep, "standby1", 7001); TEST_ASSERT_TRUE(idx >= 0); TEST_ASSERT_EQUAL_INT(1, r.nstandbys); repl_remove_standby(&r, (void*)&dummy_ep); TEST_ASSERT_EQUAL_INT(0, r.nstandbys); repl_fini(&r); printf("test_repl_add_remove_standby: PASS\n"); } static void test_repl_add_standby_max(void) { replicator_t r; int eps[MAX_STANDBYS + 1]; repl_init(&r); for (int i = 0; i < MAX_STANDBYS; i++) { int rc = repl_add_standby(&r, (void*)&eps[i], "sb", 7000 + i); TEST_ASSERT_TRUE(rc >= 0); } /* One over limit must fail */ int rc = repl_add_standby(&r, (void*)&eps[MAX_STANDBYS], "sb_extra", 9999); TEST_ASSERT_TRUE(rc < 0); repl_fini(&r); printf("test_repl_add_standby_max: PASS\n"); } static void test_repl_trim_lsn_no_standbys(void) { replicator_t r; repl_init(&r); uint64_t trim = repl_trim_lsn(&r, 42, 60); TEST_ASSERT_EQUAL_UINT64(42, trim); repl_fini(&r); printf("test_repl_trim_lsn_no_standbys: PASS\n"); } static void test_repl_trim_lsn_disconnected_slot_expiry(void) { replicator_t r; int dummy_ep = 0; repl_init(&r); repl_add_standby(&r, (void*)&dummy_ep, "sb", 7001); repl_on_ack(&r, (void*)&dummy_ep, 10); repl_remove_standby(&r, (void*)&dummy_ep); /* Immediately after disconnect: trim guard holds at last_disconnected_lsn=10 */ uint64_t trim = repl_trim_lsn(&r, 50, 60); TEST_ASSERT_EQUAL_UINT64(10, trim); /* After expiry (snap_interval_s=0 forces immediate expiry): trim == checkpoint */ repl_expire_disconnect_slots(&r, 999999, 0); trim = repl_trim_lsn(&r, 50, 0); TEST_ASSERT_EQUAL_UINT64(50, trim); repl_fini(&r); printf("test_repl_trim_lsn_disconnected_slot_expiry: PASS\n"); } /* ============================================================ * P2-3: repl_wait_ack() tests * ============================================================ */ /* Test 1: repl_wait_ack returns -ENODEV when no standbys connected */ static void test_wait_ack_no_standbys_returns_enodev(void) { replicator_t r; repl_init(&r); int rc = repl_wait_ack(&r, /*target_lsn=*/10, /*timeout_ms=*/50); TEST_ASSERT_EQUAL_INT(-ENODEV, rc); repl_fini(&r); printf("test_wait_ack_no_standbys_returns_enodev: PASS\n"); } /* Test 2: repl_wait_ack returns 0 when standby already ACKed target LSN */ static void test_wait_ack_already_acked(void) { replicator_t r; int dummy_ep = 0; repl_init(&r); repl_add_standby(&r, (void*)&dummy_ep, "sb", 7001); /* Pre-set acked_lsn to 20 (already past target 10) */ repl_on_ack(&r, (void*)&dummy_ep, 20); int rc = repl_wait_ack(&r, 10, 50); TEST_ASSERT_EQUAL_INT(0, rc); repl_fini(&r); printf("test_wait_ack_already_acked: PASS\n"); } /* Test 3: repl_wait_ack times out when standby never ACKs */ static void test_wait_ack_timeout(void) { replicator_t r; int dummy_ep = 0; repl_init(&r); repl_add_standby(&r, (void*)&dummy_ep, "sb", 7001); /* acked_lsn stays at 0 — never reaches target 100 */ struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, &start); int rc = repl_wait_ack(&r, 100, 100 /* 100ms timeout */); clock_gettime(CLOCK_MONOTONIC, &end); TEST_ASSERT_EQUAL_INT(-ETIMEDOUT, rc); /* Verify we actually waited ~100ms (not returned immediately) */ long elapsed_ms = (end.tv_sec - start.tv_sec) * 1000 + (end.tv_nsec - start.tv_nsec) / 1000000; TEST_ASSERT_TRUE(elapsed_ms >= 80); /* at least 80ms */ repl_fini(&r); printf("test_wait_ack_timeout: PASS\n"); } /* Test 4: ACK from background thread wakes repl_wait_ack */ struct ack_args { replicator_t *r; void *ep; uint64_t lsn; int delay_ms; }; static void* ack_sender_thread(void* arg) { struct ack_args* a = arg; struct timespec ts = { 0, (long)a->delay_ms * 1000000L }; nanosleep(&ts, NULL); repl_on_ack(a->r, a->ep, a->lsn); return NULL; } static void test_wait_ack_woken_by_background_ack(void) { replicator_t r; int dummy_ep = 0; repl_init(&r); repl_add_standby(&r, (void*)&dummy_ep, "sb", 7001); struct ack_args args = { &r, (void*)&dummy_ep, 15, 30 /* 30ms delay */ }; pthread_t th; pthread_create(&th, NULL, ack_sender_thread, &args); /* Wait for LSN 15 with 200ms timeout — ACK arrives after 30ms */ int rc = repl_wait_ack(&r, 15, 200); pthread_join(th, NULL); TEST_ASSERT_EQUAL_INT(0, rc); repl_fini(&r); printf("test_wait_ack_woken_by_background_ack: PASS\n"); } void test_repl_unit(void) { UNITY_BEGIN(); RUN_TEST(test_repl_init_zeroes_nstandbys); RUN_TEST(test_repl_add_remove_standby); RUN_TEST(test_repl_add_standby_max); RUN_TEST(test_repl_trim_lsn_no_standbys); RUN_TEST(test_repl_trim_lsn_disconnected_slot_expiry); RUN_TEST(test_wait_ack_no_standbys_returns_enodev); RUN_TEST(test_wait_ack_already_acked); RUN_TEST(test_wait_ack_timeout); RUN_TEST(test_wait_ack_woken_by_background_ack); UNITY_END(); }