test: P1-7 HA foundation integration tests — election, term fencing, watchdog, redirect (P1-7)
This commit is contained in:
parent
f5b0ac0af3
commit
05c00704d3
3 changed files with 154 additions and 0 deletions
|
|
@ -86,6 +86,7 @@ controller_unit_test_src = [
|
|||
'testsuite/test_ha_role.c',
|
||||
'testsuite/test_ha_cluster.c',
|
||||
'testsuite/test_ha_redirect.c',
|
||||
'testsuite/test_p1_integration.c',
|
||||
'persist.c',
|
||||
'ha.c',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ extern void test_e2e_capnp(void);
|
|||
extern void test_ha_role(void);
|
||||
extern void test_ha_cluster(void);
|
||||
extern void test_ha_redirect(void);
|
||||
extern void test_p1_integration(void);
|
||||
|
||||
void setUp(void) { return; }
|
||||
|
||||
|
|
@ -113,6 +114,7 @@ int main(int argc, char* argv[])
|
|||
TEST(test_ha_role);
|
||||
TEST(test_ha_cluster);
|
||||
TEST(test_ha_redirect);
|
||||
TEST(test_p1_integration);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
151
src/controller/testsuite/test_p1_integration.c
Normal file
151
src/controller/testsuite/test_p1_integration.c
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/* 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue