myxce/src/controller/testsuite/test_ha_cluster.c

148 lines
4.6 KiB
C

/*
* 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 <stdatomic.h>
#include <string.h>
#include <unity/unity.h>
#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);
}