myxce/src/controller/testsuite/test_ha_cluster.c
Rongsong Shen a81f2a249c fix(controller-tests): make Unity test-file attribution and counters consistent suite-wide
Only 21 of 86 test_*.c group functions called their own nested
UNITY_BEGIN()/UNITY_END(). Each nested call reset Unity's global
NumberOfTests/TestFailures counters, so controller_test.c's final
`return UNITY_END();` -- the value meson test actually gates on --
only ever reflected whichever small window of tests ran since the
LAST nested reset, silently discarding pass/fail evidence for
everything before it (measured: 16 of 972 total tests in one run).
The same nested-BEGIN calls also caused PASS/FAIL banners to show
the wrong source file for tests in files that don't reset it
themselves (Unity.TestFile just carries over from whichever group
last called UNITY_BEGIN()).

Fix: replace every nested UNITY_BEGIN() with UnitySetTestFile(__FILE__)
(sets file attribution only, no counter reset) and drop the matching
UNITY_END(); add the same UnitySetTestFile(__FILE__) as the first
statement to the other group functions that had neither. The 2
genuinely standalone binaries (test_job_array_sched_push.c,
test_job_array_apiserver_push.c -- each has its own int main(), not
registered via controller_test.c's TEST() macro) are untouched.

Verified: single consolidated summary now reads
"972 Tests 0 Failures 0 Ignored OK", exactly matching the
ground-truth RUN_TEST call-site count across all 83 registered
group files (976 total minus 4 belonging to the 2 standalone
binaries). Every PASS/FAIL banner now names its own true source
file. Full xce-build.sh all regression pass clean (Ok: 9, Fail: 0).

No production code touched -- test-harness plumbing only.
2026-07-17 14:49:12 +00:00

149 lines
4.7 KiB
C

/*
* test_ha_cluster.c — P1-5: structural tests for ha_state_t embedded in cluster_t
*
* These tests validate that:
* 1. cluster_t has an ha_state_t ha field (compile-time structural check).
* 2. ha_promote_to_primary() and ha_demote_to_standby() use c->ha (not no-ops).
*
* Tests do NOT call cluster_init() — that requires a live config/SSL/network stack.
* Instead they manually zero-init cluster_t and call ha_init() on c.ha directly.
*/
#include <stdatomic.h>
#include <string.h>
#include <unity/unity.h>
#include "ha.h"
#include "xce/cluster_def.h"
/* Valid HA config shared across tests */
static ha_config_t g_cfg = {
.heartbeat_interval_ms = 3000,
.heartbeat_timeout_ms = 10000,
.election_timeout_ms = 2000,
.probe_connect_timeout_ms = 500,
.max_promotion_retries = 5,
};
/*
* test_cluster_t_has_ha_field:
* Compile-time structural check — cluster_t must embed ha_state_t as field `ha`.
* If `c.ha` does not exist, this file will not compile → RED.
*/
static void test_cluster_t_has_ha_field(void)
{
cluster_t c;
memset(&c, 0, sizeof(c));
/* Access the field — compile error if absent */
(void)atomic_load_explicit(&c.ha.role, memory_order_acquire);
printf("test_cluster_t_has_ha_field: PASS\n");
}
/*
* test_ha_promote_to_primary_uses_c_ha:
* After ha_init(&c.ha, ...) and calling ha_promote_to_primary(&c), the role
* stored in c.ha must be CTRL_PRIMARY (not still CTRL_STANDBY).
* This would fail if ha_promote_to_primary() is still the (void)c stub.
*/
static void test_ha_promote_to_primary_uses_c_ha(void)
{
cluster_t c;
memset(&c, 0, sizeof(c));
/* Manually initialise only the HA field — no cluster_init() required */
TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0 /* my_priority = 0 → primary */));
TEST_ASSERT_EQUAL_INT(CTRL_STANDBY,
(int)atomic_load_explicit(&c.ha.role, memory_order_acquire));
/* c.hooksys is zeroed — hooks_registered guard prevents xce_add_hook() call */
ha_promote_to_primary(&c);
TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY,
(int)atomic_load_explicit(&c.ha.role, memory_order_acquire));
ha_fini(&c.ha);
printf("test_ha_promote_to_primary_uses_c_ha: PASS\n");
}
/*
* test_ha_demote_to_standby_uses_c_ha:
* Promote then demote. Role must end as CTRL_STANDBY.
*/
static void test_ha_demote_to_standby_uses_c_ha(void)
{
cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0));
ha_promote_to_primary(&c);
TEST_ASSERT_EQUAL_INT(CTRL_PRIMARY,
(int)atomic_load_explicit(&c.ha.role, memory_order_acquire));
ha_demote_to_standby(&c);
TEST_ASSERT_EQUAL_INT(CTRL_STANDBY,
(int)atomic_load_explicit(&c.ha.role, memory_order_acquire));
ha_fini(&c.ha);
printf("test_ha_demote_to_standby_uses_c_ha: PASS\n");
}
/*
* test_ha_promote_increments_term_via_cluster:
* Each ha_promote_to_primary() call must increment the election term stored
* in c.ha.term, because ha_do_promote() is called inside.
*/
static void test_ha_promote_increments_term_via_cluster(void)
{
cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0));
uint64_t term0 = atomic_load_explicit(&c.ha.term, memory_order_acquire);
ha_promote_to_primary(&c);
uint64_t term1 = atomic_load_explicit(&c.ha.term, memory_order_acquire);
TEST_ASSERT_TRUE(term1 > term0);
ha_fini(&c.ha);
printf("test_ha_promote_increments_term_via_cluster: PASS\n");
}
/*
* test_hooks_registered_guard_prevents_double_register:
* When hooksys is zeroed (no real hook system) and hooks_registered is 0,
* ha_promote_to_primary() must set hooks_registered = 1 so a second
* call skips the registration path.
*/
static void test_hooks_registered_guard_prevents_double_register(void)
{
cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, ha_init(&c.ha, &g_cfg, 0));
TEST_ASSERT_EQUAL_INT(0, c.ha.hooks_registered);
ha_promote_to_primary(&c);
/* hooks_registered must now be 1 (idempotency guard set) */
TEST_ASSERT_EQUAL_INT(1, c.ha.hooks_registered);
/* Second promote: must not crash even with zeroed hooksys */
ha_promote_to_primary(&c);
TEST_ASSERT_EQUAL_INT(1, c.ha.hooks_registered);
ha_fini(&c.ha);
printf("test_hooks_registered_guard_prevents_double_register: PASS\n");
}
void test_ha_cluster(void)
{
UnitySetTestFile(__FILE__);
RUN_TEST(test_cluster_t_has_ha_field);
RUN_TEST(test_ha_promote_to_primary_uses_c_ha);
RUN_TEST(test_ha_demote_to_standby_uses_c_ha);
RUN_TEST(test_ha_promote_increments_term_via_cluster);
RUN_TEST(test_hooks_registered_guard_prevents_double_register);
}