forked from lei.chen/myxce
151 lines
5.1 KiB
C
151 lines
5.1 KiB
C
/* P1-7: HA Foundation integration tests — assembled system */
|
|
#include <stdatomic.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "ha.h"
|
|
|
|
/* Test 1: Single-node startup: after ha_init + ha_do_promote(priority=0),
|
|
* role must be PRIMARY */
|
|
static void test_single_node_starts_as_primary(void)
|
|
{
|
|
ha_state_t ha;
|
|
ha_config_t cfg = {
|
|
.heartbeat_interval_ms = 3000,
|
|
.heartbeat_timeout_ms = 10000,
|
|
.election_timeout_ms = 2000,
|
|
.probe_connect_timeout_ms = 500,
|
|
.max_promotion_retries = 5,
|
|
};
|
|
ha_init(&ha, &cfg, 0 /* priority 0 = highest */);
|
|
/* Simulate am_i_primary() decision for priority-0 node */
|
|
ha_do_promote(&ha);
|
|
TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY,
|
|
(int)atomic_load_explicit(&ha.role, memory_order_acquire));
|
|
ha_fini(&ha);
|
|
printf("test_single_node_starts_as_primary: PASS\n");
|
|
}
|
|
|
|
/* Test 2: Term fencing — promote increments term; second promote increments again */
|
|
static void test_term_fencing_monotonic(void)
|
|
{
|
|
ha_state_t ha;
|
|
ha_config_t cfg = {
|
|
.heartbeat_interval_ms = 3000,
|
|
.heartbeat_timeout_ms = 10000,
|
|
.election_timeout_ms = 2000,
|
|
.probe_connect_timeout_ms = 500,
|
|
.max_promotion_retries = 5,
|
|
};
|
|
ha_init(&ha, &cfg, 0);
|
|
uint64_t t0 = atomic_load_explicit(&ha.term, memory_order_acquire);
|
|
ha_do_promote(&ha);
|
|
uint64_t t1 = atomic_load_explicit(&ha.term, memory_order_acquire);
|
|
ha_do_demote(&ha);
|
|
ha_do_promote(&ha);
|
|
uint64_t t2 = atomic_load_explicit(&ha.term, memory_order_acquire);
|
|
TEST_ASSERT_TRUE(t1 > t0);
|
|
TEST_ASSERT_TRUE(t2 > t1);
|
|
ha_fini(&ha);
|
|
printf("test_term_fencing_monotonic: PASS\n");
|
|
}
|
|
|
|
/* Test 3: Watchdog start+stop cycle — verify clean teardown */
|
|
static void test_watchdog_lifecycle_clean(void)
|
|
{
|
|
ha_state_t ha;
|
|
ha_config_t cfg = {
|
|
.heartbeat_interval_ms = 3000,
|
|
.heartbeat_timeout_ms = 10000,
|
|
.election_timeout_ms = 2000,
|
|
.probe_connect_timeout_ms = 500,
|
|
.max_promotion_retries = 5,
|
|
};
|
|
ha_init(&ha, &cfg, 0);
|
|
TEST_ASSERT_EQUAL_INT(0, ha_start_watchdog(&ha));
|
|
struct timespec ts = { 0, 10 * 1000000L }; /* 10 ms */
|
|
nanosleep(&ts, NULL);
|
|
ha_stop_watchdog(&ha);
|
|
/* After stop, watchdog_thread must be reset to 0 */
|
|
TEST_ASSERT_EQUAL_INT(0, (int)ha.watchdog_thread);
|
|
ha_fini(&ha);
|
|
printf("test_watchdog_lifecycle_clean: PASS\n");
|
|
}
|
|
|
|
/* Test 4: Redirect guard — primary never redirects; standby with known
|
|
* primary redirects; standby with no known primary reports nomaster */
|
|
static void test_redirect_guard_integration(void)
|
|
{
|
|
ha_config_t cfg = {
|
|
.heartbeat_interval_ms = 3000,
|
|
.heartbeat_timeout_ms = 10000,
|
|
.election_timeout_ms = 2000,
|
|
.probe_connect_timeout_ms = 500,
|
|
.max_promotion_retries = 5,
|
|
};
|
|
|
|
/* Primary: no redirect */
|
|
ha_state_t ha_primary;
|
|
ha_init(&ha_primary, &cfg, 0);
|
|
atomic_store_explicit(&ha_primary.role, (int32_t)CTRL_PRIMARY,
|
|
memory_order_release);
|
|
TEST_ASSERT_EQUAL_INT(0, ha_should_redirect(&ha_primary));
|
|
|
|
/* Standby with known primary: redirect */
|
|
ha_state_t ha_standby;
|
|
ha_init(&ha_standby, &cfg, 1);
|
|
strncpy(ha_standby.primary_host, "10.0.0.1",
|
|
sizeof(ha_standby.primary_host) - 1);
|
|
ha_standby.primary_port = 7000;
|
|
TEST_ASSERT_EQUAL_INT(1, ha_should_redirect(&ha_standby));
|
|
TEST_ASSERT_EQUAL_INT(0, ha_no_primary_known(&ha_standby));
|
|
|
|
/* Standby with no known primary: no redirect, nomaster */
|
|
ha_state_t ha_cold;
|
|
ha_init(&ha_cold, &cfg, 2);
|
|
TEST_ASSERT_EQUAL_INT(0, ha_should_redirect(&ha_cold));
|
|
TEST_ASSERT_EQUAL_INT(1, ha_no_primary_known(&ha_cold));
|
|
|
|
ha_fini(&ha_primary);
|
|
ha_fini(&ha_standby);
|
|
ha_fini(&ha_cold);
|
|
printf("test_redirect_guard_integration: PASS\n");
|
|
}
|
|
|
|
/* Test 5: Hook idempotency — double-promote does not register hooks twice
|
|
* (full hook registration needs a cluster_t — this tests the hooks_registered
|
|
* guard) */
|
|
static void test_hooks_registered_guard_integration(void)
|
|
{
|
|
ha_state_t ha;
|
|
ha_config_t cfg = {
|
|
.heartbeat_interval_ms = 3000,
|
|
.heartbeat_timeout_ms = 10000,
|
|
.election_timeout_ms = 2000,
|
|
.probe_connect_timeout_ms = 500,
|
|
.max_promotion_retries = 5,
|
|
};
|
|
ha_init(&ha, &cfg, 0);
|
|
TEST_ASSERT_EQUAL_INT(0, ha.hooks_registered);
|
|
/* Simulate idempotency: hooks_registered = 1 after first promote */
|
|
ha.hooks_registered = 1;
|
|
/* Second promote should see guard and skip re-registration */
|
|
TEST_ASSERT_TRUE(ha.hooks_registered == 1); /* guard still set */
|
|
/* Demote resets guard */
|
|
ha_do_demote(&ha);
|
|
TEST_ASSERT_EQUAL_INT(0, ha.hooks_registered);
|
|
ha_fini(&ha);
|
|
printf("test_hooks_registered_guard_integration: PASS\n");
|
|
}
|
|
|
|
void test_p1_integration(void)
|
|
{
|
|
RUN_TEST(test_single_node_starts_as_primary);
|
|
RUN_TEST(test_term_fencing_monotonic);
|
|
RUN_TEST(test_watchdog_lifecycle_clean);
|
|
RUN_TEST(test_redirect_guard_integration);
|
|
RUN_TEST(test_hooks_registered_guard_integration);
|
|
}
|