/* * test_ha_cluster.c — P1-5: structural tests for ha_state_t embedded in cluster_t * * These tests validate that: * 1. cluster_t has an ha_state_t ha field (compile-time structural check). * 2. ha_promote_to_primary() and ha_demote_to_standby() use c->ha (not no-ops). * * Tests do NOT call cluster_init() — that requires a live config/SSL/network stack. * Instead they manually zero-init cluster_t and call ha_init() on c.ha directly. */ #include #include #include #include "ha.h" #include "xce/cluster_def.h" /* Valid HA config shared across tests */ static ha_config_t g_cfg = { .heartbeat_interval_ms = 3000, .heartbeat_timeout_ms = 10000, .election_timeout_ms = 2000, .probe_connect_timeout_ms = 500, .max_promotion_retries = 5, }; /* * test_cluster_t_has_ha_field: * Compile-time structural check — cluster_t must embed ha_state_t as field `ha`. * If `c.ha` does not exist, this file will not compile → RED. */ static void test_cluster_t_has_ha_field(void) { cluster_t c; memset(&c, 0, sizeof(c)); /* Access the field — compile error if absent */ (void)atomic_load_explicit(&c.ha.role, memory_order_acquire); printf("test_cluster_t_has_ha_field: PASS\n"); } /* * test_ha_promote_to_primary_uses_c_ha: * After ha_init(&c.ha, ...) and calling ha_promote_to_primary(&c), the role * stored in c.ha must be CTRL_PRIMARY (not still CTRL_STANDBY). * This would fail if ha_promote_to_primary() is still the (void)c stub. */ static void test_ha_promote_to_primary_uses_c_ha(void) { cluster_t c; memset(&c, 0, sizeof(c)); /* Manually initialise only the HA field — no cluster_init() required */ TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0 /* my_priority = 0 → primary */)); TEST_ASSERT_EQUAL_INT(CTRL_STANDBY, (int)atomic_load_explicit(&c.ha.role, memory_order_acquire)); /* c.hooksys is zeroed — hooks_registered guard prevents xce_add_hook() call */ ha_promote_to_primary(&c); TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY, (int)atomic_load_explicit(&c.ha.role, memory_order_acquire)); ha_fini(&c.ha); printf("test_ha_promote_to_primary_uses_c_ha: PASS\n"); } /* * test_ha_demote_to_standby_uses_c_ha: * Promote then demote. Role must end as CTRL_STANDBY. */ static void test_ha_demote_to_standby_uses_c_ha(void) { cluster_t c; memset(&c, 0, sizeof(c)); TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0)); ha_promote_to_primary(&c); TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY, (int)atomic_load_explicit(&c.ha.role, memory_order_acquire)); ha_demote_to_standby(&c); TEST_ASSERT_EQUAL_INT(CTRL_STANDBY, (int)atomic_load_explicit(&c.ha.role, memory_order_acquire)); ha_fini(&c.ha); printf("test_ha_demote_to_standby_uses_c_ha: PASS\n"); } /* * test_ha_promote_increments_term_via_cluster: * Each ha_promote_to_primary() call must increment the election term stored * in c.ha.term, because ha_do_promote() is called inside. */ static void test_ha_promote_increments_term_via_cluster(void) { cluster_t c; memset(&c, 0, sizeof(c)); TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0)); uint64_t term0 = atomic_load_explicit(&c.ha.term, memory_order_acquire); ha_promote_to_primary(&c); uint64_t term1 = atomic_load_explicit(&c.ha.term, memory_order_acquire); TEST_ASSERT_TRUE(term1 > term0); ha_fini(&c.ha); printf("test_ha_promote_increments_term_via_cluster: PASS\n"); } /* * test_hooks_registered_guard_prevents_double_register: * When hooksys is zeroed (no real hook system) and hooks_registered is 0, * ha_promote_to_primary() must set hooks_registered = 1 so a second * call skips the registration path. */ static void test_hooks_registered_guard_prevents_double_register(void) { cluster_t c; memset(&c, 0, sizeof(c)); TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0)); TEST_ASSERT_EQUAL_INT(0, c.ha.hooks_registered); ha_promote_to_primary(&c); /* hooks_registered must now be 1 (idempotency guard set) */ TEST_ASSERT_EQUAL_INT(1, c.ha.hooks_registered); /* Second promote: must not crash even with zeroed hooksys */ ha_promote_to_primary(&c); TEST_ASSERT_EQUAL_INT(1, c.ha.hooks_registered); ha_fini(&c.ha); printf("test_hooks_registered_guard_prevents_double_register: PASS\n"); } void test_ha_cluster(void) { RUN_TEST(test_cluster_t_has_ha_field); RUN_TEST(test_ha_promote_to_primary_uses_c_ha); RUN_TEST(test_ha_demote_to_standby_uses_c_ha); RUN_TEST(test_ha_promote_increments_term_via_cluster); RUN_TEST(test_hooks_registered_guard_prevents_double_register); }