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

@ -467,7 +467,7 @@ static void test_noop_classify_is_retain(void)
*
* Contract (design §Promotion Rewrite, Reqs 1.1, 1.3, 1.4, 1.5, 2.1, 2.2, 2.3):
* every road to PRIMARY lands the node DIRECTLY in CTRL_PRIMARY — no
* CTRL_PRIMARY_COMMITTING ever observable — registers the persist hooks,
* transitional leader sub-state ever observable — registers the persist hooks,
* arms NO wall-clock watchdog, and (production, c != NULL) appends a WAL_NOOP
* of the current term then sets the commit engine's own-term gate
* (commit_set_writable_lsn) to that no-op's LSN so confirmation of any
@ -477,10 +477,10 @@ static void test_noop_classify_is_retain(void)
* - Cases 1-3 drive the raft become-leader callback via its existing
* XCE_TESTING shim `raft_cb_become_leader_for_test(ha, term)` (ha.c:3121),
* which calls `raft_cb_become_leader` with `ha->raft_c = NULL`. This is
* THE seam test_ha_evaluate_committing.c / test_ha_connection_reuse.c use.
* RED today: that path stores CTRL_PRIMARY_COMMITTING (ha.c:514), so the
* direct-PRIMARY / is_primary / no-redirect / hooks-registered assertions
* fail. (The `c == NULL` shim also means the watchdog-arm + noop/gate
* THE seam test_ha_connection_reuse.c uses. The contract: that path
* lands the node directly in CTRL_PRIMARY, so the direct-PRIMARY /
* is_primary / no-redirect / hooks-registered assertions hold. (The
* `c == NULL` shim also means the noop/gate
* blocks — both under `if (c)` — are skipped, so they are NOT observable
* through THIS seam; see cases 4-7 for the real-cluster seam.)
* - Cases 4-7 drive the SHARED promotion sequence `ha_promote_to_primary(c)`
@ -522,7 +522,6 @@ static ha_config_t lifecycle_ha_cfg(void)
.election_timeout_ms = 2000,
.probe_connect_timeout_ms = 500,
.max_promotion_retries = 5,
.committing_timeout_ms = HA_DEFAULT_COMMITTING_TIMEOUT_MS,
};
return cfg;
}
@ -543,7 +542,7 @@ static int delaymgr_task_count(const delaymgr_t *mgr)
/* ── Case 1: DIRECT PRIMARY — become-leader lands in CTRL_PRIMARY, not a
* transitional state; ha_is_primary() is true immediately on return (Req 1.1,
* 1.4). RED today: raft_cb_become_leader stores CTRL_PRIMARY_COMMITTING. */
* 1.4). raft_cb_become_leader stores CTRL_PRIMARY directly. */
static void test_promotion_lands_directly_in_primary(void)
{
#ifdef XCE_TESTING
@ -560,8 +559,7 @@ static void test_promotion_lands_directly_in_primary(void)
ctrl_role_t role =
(ctrl_role_t)atomic_load_explicit(&ha.role, memory_order_acquire);
TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY, (int)role,
"winning election must land DIRECTLY in CTRL_PRIMARY (Req 1.1); "
"current code stores CTRL_PRIMARY_COMMITTING — RED");
"winning election must land DIRECTLY in CTRL_PRIMARY (Req 1.1)");
TEST_ASSERT_EQUAL_INT_MESSAGE(1, ha_is_primary(&ha),
"ha_is_primary() must be true the instant promotion returns (Req 1.4)");
@ -573,9 +571,10 @@ static void test_promotion_lands_directly_in_primary(void)
}
/* ── Case 2: NO REDIRECT — ha_should_redirect() returns 0 the instant
* promotion returns, before any standby ACK (Req 1.3). RED today: in
* CTRL_PRIMARY_COMMITTING ha_should_redirect can return 1 (a live election
* winner pointing clients at a primary that "does not exist yet"). */
* promotion returns, before any standby ACK (Req 1.3). A live election winner
* is CTRL_PRIMARY, so it never redirects clients to a primary that "does not
* exist yet" — the redirect-loop window cannot exist once the single leader
* role is the only leader role. */
static void test_promotion_no_redirect_from_live_primary(void)
{
#ifdef XCE_TESTING
@ -594,11 +593,10 @@ static void test_promotion_no_redirect_from_live_primary(void)
raft_cb_become_leader_for_test(&ha, /*term=*/1);
/* Assert: a live primary never redirects (Req 1.3). In CTRL_PRIMARY the
* stale hint is ignored; in CTRL_PRIMARY_COMMITTING (today) the hint makes
* ha_should_redirect return 1 → RED. */
* stale hint is ignored — a node that is itself the primary cannot satisfy
* the redirect condition. */
TEST_ASSERT_EQUAL_INT_MESSAGE(0, ha_should_redirect(&ha),
"a freshly promoted primary must NOT redirect clients (Req 1.3); "
"current COMMITTING role + a primary hint yields redirect=1 — RED");
"a freshly promoted primary must NOT redirect clients (Req 1.3)");
ha_fini(&ha);
printf("test_promotion_no_redirect_from_live_primary: PASS\n");
@ -1074,9 +1072,9 @@ static void test_standalone_promotion_gate_opens_immediately(void)
*
* Pin: the legacy real-cluster path produces role/hook/gate/no-watchdog state
* identical to the raft road's role and to ha_promote_to_primary's full state.
* RED before task 3.1: the legacy branch stored CTRL_PRIMARY_COMMITTING, reset
* repl_ack_count, and armed the committing watchdog — diverging from the raft
* road on every axis. */
* Both roads land directly in CTRL_PRIMARY, arm the own-term gate, and arm no
* wall-clock watchdog — symmetric on every axis (the former transitional-state
* divergence, with its ack counter and watchdog, is gone). */
static void test_legacy_and_raft_promotion_are_symmetric(void)
{
#ifdef XCE_TESTING