test(ha-single-leader-role): sweep all COMMITTING fixtures to direct-PRIMARY contract (task 5.1)

This commit is contained in:
Rongsong Shen 2026-06-13 13:59:42 +00:00
parent 71ce41f486
commit 7f974e766b
11 changed files with 138 additions and 169 deletions

View file

@ -1,17 +1,18 @@
/*
* test_ha_repl_push.c — Unity tests for Task 4.2:
* Config and WAL replication push to standbys in the COMMITTING path.
* Config and WAL replication push to standbys on election win (the node
* becomes CTRL_PRIMARY directly — single leader role, ha-single-leader-role).
*
* Tests verify:
* Req 7.1: When entering CTRL_PRIMARY_COMMITTING, push_config_to_standbys
* Req 7.1: When the node becomes CTRL_PRIMARY, push_config_to_standbys
* is called (via ha_push_config_hook seam).
* Req 7.2: When entering CTRL_PRIMARY_COMMITTING, ha_send_ctrlhello_to_all_standbys
* Req 7.2: When the node becomes CTRL_PRIMARY, ha_send_ctrlhello_to_all_standbys
* is called to trigger catch-up replication (via ha_send_ctrlhello_hook seam).
* Req 7.3: The push is triggered at the point of COMMITTING entry, not before.
* Req 7.3: The push is triggered at the point of promotion, not before.
* When election is lost (CTRL_STANDBY), no push occurs.
*
* Design ref: design.md "ha_send_ctrlhello_to_all_standbys" section
* Boundary: ha.c (election evaluation + COMMITTING path)
* Boundary: ha.c (election evaluation + direct-PRIMARY promotion path)
* Requirements: 7.1, 7.2, 7.3
*/
#define _GNU_SOURCE
@ -31,7 +32,7 @@
/* ── Test-only seam: ha_send_ctrlhello_to_all_standbys call hook ─────────────
*
* In production, ha_elect_evaluate_and_signal calls
* ha_send_ctrlhello_to_all_standbys(c) in the COMMITTING path.
* ha_send_ctrlhello_to_all_standbys(c) on the promotion path.
* For unit tests, the real function is not linked (cluster.c not in test binary).
* The XCE_TESTING seam in ha.c checks ha_send_ctrlhello_hook; if non-NULL, it
* calls the hook instead of the real ha_send_ctrlhello_to_all_standbys.
@ -41,7 +42,7 @@ extern int (*ha_send_ctrlhello_hook)(void *c);
/* ── Test-only seam: push_config_to_standbys call hook ──────────────────────
*
* In production, ha_elect_evaluate_and_signal calls push_config_to_standbys(c,...)
* in the COMMITTING path. For unit tests, the seam in ha.c checks
* on the promotion path. For unit tests, the seam in ha.c checks
* ha_push_config_hook; if non-NULL, it calls the hook instead.
*/
extern int (*ha_push_config_hook)(void *c);
@ -83,7 +84,6 @@ static ha_config_t g_cfg = {
.max_promotion_retries = 5,
.election_jitter_max_ms = 0,
.election_priority_step_ms = 500,
.committing_timeout_ms = 10000,
};
/* ── Helper: build a minimal ha_elect_ctx_t ─────────────────────────────── */
@ -136,12 +136,12 @@ static void clear_hooks(void)
ha_phase2_hook = NULL;
}
/* ── Test 1: Req 7.2 — COMMITTING path calls ha_send_ctrlhello_to_all_standbys ── */
/* ── Test 1: Req 7.2 — promotion calls ha_send_ctrlhello_to_all_standbys ───── */
static void test_committing_calls_ctrlhello_push(void)
static void test_promotion_calls_ctrlhello_push(void)
{
/* When election is won (CTRL_PRIMARY → COMMITTING), ha_send_ctrlhello_to_all_standbys
* must be called exactly once.
/* When election is won (node becomes CTRL_PRIMARY directly),
* ha_send_ctrlhello_to_all_standbys must be called exactly once.
* Single-node wins: n_other=0, votes_granted=1. */
reset_counters();
@ -156,18 +156,18 @@ static void test_committing_calls_ctrlhello_push(void)
ha_elect_evaluate_and_signal(&ha, ctx);
TEST_ASSERT_EQUAL_INT_MESSAGE(1, g_ctrlhello_call_count,
"ha_send_ctrlhello_to_all_standbys must be called once on COMMITTING entry (Req 7.2)");
"ha_send_ctrlhello_to_all_standbys must be called once on promotion (Req 7.2)");
free_ctx(ctx);
ha_fini(&ha);
clear_hooks();
printf("test_committing_calls_ctrlhello_push: PASS (ctrlhello_calls=%d)\n",
printf("test_promotion_calls_ctrlhello_push: PASS (ctrlhello_calls=%d)\n",
g_ctrlhello_call_count);
}
/* ── Test 2: Req 7.1 — COMMITTING path calls push_config_to_standbys ──────── */
/* ── Test 2: Req 7.1 — promotion calls push_config_to_standbys ────────────── */
static void test_committing_calls_push_config(void)
static void test_promotion_calls_push_config(void)
{
/* When election is won, push_config_to_standbys must be called once. */
reset_counters();
@ -183,12 +183,12 @@ static void test_committing_calls_push_config(void)
ha_elect_evaluate_and_signal(&ha, ctx);
TEST_ASSERT_EQUAL_INT_MESSAGE(1, g_push_config_call_count,
"push_config_to_standbys must be called once on COMMITTING entry (Req 7.1)");
"push_config_to_standbys must be called once on promotion (Req 7.1)");
free_ctx(ctx);
ha_fini(&ha);
clear_hooks();
printf("test_committing_calls_push_config: PASS (push_config_calls=%d)\n",
printf("test_promotion_calls_push_config: PASS (push_config_calls=%d)\n",
g_push_config_call_count);
}
@ -251,8 +251,8 @@ static void test_3node_win_calls_both_pushes(void)
ctrl_role_t actual_role =
(ctrl_role_t)atomic_load_explicit(&ha.role, memory_order_acquire);
TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY_COMMITTING, (int)actual_role,
"ha->role must be CTRL_PRIMARY_COMMITTING after winning");
TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY, (int)actual_role,
"ha->role must be CTRL_PRIMARY directly after winning (single leader role, Req 1.1)");
TEST_ASSERT_EQUAL_INT_MESSAGE(1, g_ctrlhello_call_count,
"ha_send_ctrlhello_to_all_standbys must be called once (3-node win)");
@ -303,8 +303,8 @@ static void test_done_posted_prevents_double_push(void)
void test_ha_repl_push(void)
{
RUN_TEST(test_committing_calls_ctrlhello_push);
RUN_TEST(test_committing_calls_push_config);
RUN_TEST(test_promotion_calls_ctrlhello_push);
RUN_TEST(test_promotion_calls_push_config);
RUN_TEST(test_standby_result_does_not_push);
RUN_TEST(test_3node_win_calls_both_pushes);
RUN_TEST(test_done_posted_prevents_double_push);