diff --git a/src/controller/ha.c b/src/controller/ha.c index 04face0e..874a3da4 100644 --- a/src/controller/ha.c +++ b/src/controller/ha.c @@ -1638,6 +1638,29 @@ int election_timeout_fn_for_test(ha_state_t *ha, ha_elect_ctx_t *ctx, pthread_mutex_unlock(&ctx->lock); return 0; } + +/* + * committing_timeout_fn_for_test: test helper that exercises the committing + * timeout callback logic with an explicit cancelled_val. The cluster pointer + * may be NULL; the function checks the role on `ha` and calls + * ha_promote_to_primary(c) only when role == CTRL_PRIMARY_COMMITTING && c != NULL. + * + * Unlike the production callback, this shim does NOT free the ctx structure — + * it is stack/test-owned and must not be heap-freed by the shim. + * + * Returns 0 in all cases (mirrors the real callback). + */ +int committing_timeout_fn_for_test(ha_state_t *ha, cluster_t *c, + int cancelled_val) +{ + if (cancelled_val) + return 0; + ctrl_role_t role = (ctrl_role_t)atomic_load_explicit(&ha->role, + memory_order_acquire); + if (role == CTRL_PRIMARY_COMMITTING && c != NULL) + ha_promote_to_primary(c); + return 0; +} #endif /* XCE_TESTING */ /* ── Task 2.4: election_timeout_fn — delaymgr callback ───────────────────── diff --git a/src/controller/testsuite/test_ha_evaluate_committing.c b/src/controller/testsuite/test_ha_evaluate_committing.c index fd55ac3a..7146d271 100644 --- a/src/controller/testsuite/test_ha_evaluate_committing.c +++ b/src/controller/testsuite/test_ha_evaluate_committing.c @@ -26,6 +26,7 @@ #include #include "ha.h" +#include "xce/cluster_def.h" /* full cluster_t layout for committing_timeout_fn tests */ /* ── Test-only seam: cluster_init_phase2 call counter ─────────────────────── * @@ -366,6 +367,66 @@ static void test_done_posted_prevents_double_execute(void) printf("test_done_posted_prevents_double_execute: PASS (calls=%d)\n", g_phase2_call_count); } +/* ── Test 9: committing_timeout_fn fires ha_promote_to_primary ───────────── */ + +static void test_committing_timeout_fn_promotes_to_primary(void) +{ + /* Req 4.3, 8.4: when not cancelled and role == CTRL_PRIMARY_COMMITTING, + * committing_timeout_fn must call ha_promote_to_primary, which drives + * c->ha.role to CTRL_PRIMARY (degraded promotion). */ + reset_counters(); + + /* Zero-init cluster: xce_add_hook returns -EINVAL harmlessly on zeroed + * hooksys, so ha_promote_to_primary is safe to call (comment at ha.c:603). */ + cluster_t c; + memset(&c, 0, sizeof(c)); + ha_init(&c.ha, &g_cfg, 0); + atomic_store_explicit(&c.ha.role, (int32_t)CTRL_PRIMARY_COMMITTING, + memory_order_release); + + extern int committing_timeout_fn_for_test(ha_state_t *ha, cluster_t *c, + int cancelled_val); + committing_timeout_fn_for_test(&c.ha, &c, 0 /* not cancelled */); + + ctrl_role_t role = + (ctrl_role_t)atomic_load_explicit(&c.ha.role, memory_order_acquire); + TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY, (int)role, + "committing_timeout_fn (not cancelled, COMMITTING role) must promote to CTRL_PRIMARY"); + + ha_fini(&c.ha); + clear_hook(); + printf("test_committing_timeout_fn_promotes_to_primary: PASS (role=%d==CTRL_PRIMARY)\n", + (int)role); +} + +/* ── Test 10: cancelled committing_timeout_fn → no role change ───────────── */ + +static void test_committing_timeout_fn_cancelled_is_noop(void) +{ + /* Req 4.3: when cancelled=1, committing_timeout_fn must be a no-op; + * ha->role must remain CTRL_PRIMARY_COMMITTING. */ + reset_counters(); + + cluster_t c; + memset(&c, 0, sizeof(c)); + ha_init(&c.ha, &g_cfg, 0); + atomic_store_explicit(&c.ha.role, (int32_t)CTRL_PRIMARY_COMMITTING, + memory_order_release); + + extern int committing_timeout_fn_for_test(ha_state_t *ha, cluster_t *c, + int cancelled_val); + committing_timeout_fn_for_test(&c.ha, &c, 1 /* cancelled */); + + ctrl_role_t role = + (ctrl_role_t)atomic_load_explicit(&c.ha.role, memory_order_acquire); + TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY_COMMITTING, (int)role, + "cancelled committing_timeout_fn must not change ha->role"); + + ha_fini(&c.ha); + clear_hook(); + printf("test_committing_timeout_fn_cancelled_is_noop: PASS (role still CTRL_PRIMARY_COMMITTING)\n"); +} + /* ── Suite entry point ───────────────────────────────────────────────────── */ void test_ha_evaluate_committing(void) @@ -378,4 +439,6 @@ void test_ha_evaluate_committing(void) RUN_TEST(test_election_timeout_fn_fires_evaluate); RUN_TEST(test_cancelled_timeout_is_noop); RUN_TEST(test_done_posted_prevents_double_execute); + RUN_TEST(test_committing_timeout_fn_promotes_to_primary); + RUN_TEST(test_committing_timeout_fn_cancelled_is_noop); }