test(ha-single-leader-role): sweep all COMMITTING fixtures to direct-PRIMARY contract (task 5.1)
This commit is contained in:
parent
71ce41f486
commit
7f974e766b
11 changed files with 138 additions and 169 deletions
|
|
@ -546,34 +546,43 @@ static void test_dispatch_gate_init_allows_heartbeat(void)
|
|||
printf("test_dispatch_gate_init_allows_heartbeat: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Task 3.1: dispatch gate CTRL_PRIMARY_COMMITTING ─────────────────────── */
|
||||
/* ── Task 3.1: dispatch gate — primary without write quorum (single leader
|
||||
* role; the former transitional state is gone, ha-single-leader-role Req 1.2).
|
||||
* A multi-node primary with no connected standbys lacks write quorum
|
||||
* (n_peers=2, standby_endpoints empty → ha_has_write_quorum false), so it
|
||||
* applies the universal commit barrier: job ops are not-ready, replication ops
|
||||
* that build majority are permitted. */
|
||||
|
||||
static void test_dispatch_gate_committing_denies_job_ops(void)
|
||||
static void test_dispatch_gate_primary_no_quorum_denies_job_ops(void)
|
||||
{
|
||||
/* Req 5.1, 8.1: When role is CTRL_PRIMARY_COMMITTING, primary-only
|
||||
* job operations must be denied with XCE_ERR_NOT_READY.
|
||||
/* Req 5.1, 8.1 + commit barrier: a primary lacking write quorum denies
|
||||
* primary-only job operations with XCE_ERR_NOT_READY.
|
||||
* OPCode_jobsub is a representative job-submission opcode. */
|
||||
ha_state_t ha;
|
||||
ha_init(&ha, &g_dispatch_cfg, 0);
|
||||
atomic_store_explicit(&ha.role, (int32_t)CTRL_PRIMARY_COMMITTING,
|
||||
atomic_store_explicit(&ha.role, (int32_t)CTRL_PRIMARY,
|
||||
memory_order_release);
|
||||
/* Multi-node cluster with zero connected standbys → no write quorum. */
|
||||
ha.n_peers = 2;
|
||||
|
||||
int rc = ha_dispatch_gate(&ha, OPCode_jobsub);
|
||||
TEST_ASSERT_EQUAL_INT(-XCE_ERR_NOT_READY, rc);
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_dispatch_gate_committing_denies_job_ops: PASS\n");
|
||||
printf("test_dispatch_gate_primary_no_quorum_denies_job_ops: PASS\n");
|
||||
}
|
||||
|
||||
static void test_dispatch_gate_committing_allows_repl_ops(void)
|
||||
static void test_dispatch_gate_primary_no_quorum_allows_repl_ops(void)
|
||||
{
|
||||
/* Req 5.2, 8.1: When role is CTRL_PRIMARY_COMMITTING, replication
|
||||
* operations must be permitted (return 0).
|
||||
* OPCode_replCatchUpDone is the key signal that ends the COMMITTING phase. */
|
||||
/* Req 5.2, 8.1: a primary lacking write quorum still permits replication
|
||||
* operations (return 0) — they are how the cluster rebuilds majority.
|
||||
* OPCode_replCatchUpDone is the catch-up completion signal. */
|
||||
ha_state_t ha;
|
||||
ha_init(&ha, &g_dispatch_cfg, 0);
|
||||
atomic_store_explicit(&ha.role, (int32_t)CTRL_PRIMARY_COMMITTING,
|
||||
atomic_store_explicit(&ha.role, (int32_t)CTRL_PRIMARY,
|
||||
memory_order_release);
|
||||
/* Multi-node cluster with zero connected standbys → no write quorum. */
|
||||
ha.n_peers = 2;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, ha_dispatch_gate(&ha, OPCode_replCatchUpDone));
|
||||
TEST_ASSERT_EQUAL_INT(0, ha_dispatch_gate(&ha, OPCode_heartbeat));
|
||||
|
|
@ -586,7 +595,7 @@ static void test_dispatch_gate_committing_allows_repl_ops(void)
|
|||
TEST_ASSERT_EQUAL_INT(0, ha_dispatch_gate(&ha, OPCode_replAck));
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_dispatch_gate_committing_allows_repl_ops: PASS\n");
|
||||
printf("test_dispatch_gate_primary_no_quorum_allows_repl_ops: PASS\n");
|
||||
}
|
||||
|
||||
/* ── Task 1.1 (ha-heartbeat-liveness): ha_init() zero-initializes last_activity ─
|
||||
|
|
@ -615,8 +624,8 @@ static void test_ha_init_last_activity_is_zero(void)
|
|||
* These tests verify the call-sequence contract by checking that:
|
||||
* 1. ha_start_election_async with n_peers=1 sets ha->n_peers = 1 (single-node
|
||||
* path — the same path myapp_run takes for a single-node cluster).
|
||||
* 2. The role after ha_start_election_async(n_peers=1) is CTRL_PRIMARY_COMMITTING
|
||||
* (not CTRL_PRIMARY) — matching what myapp_run would produce in the new design.
|
||||
* 2. The role after ha_start_election_async(n_peers=1) is CTRL_PRIMARY
|
||||
* directly (single leader role) — matching what myapp_run would produce.
|
||||
* 3. ha_start_election_async completes in < 10ms — confirming myapp_run does not
|
||||
* block before entering cluster_run.
|
||||
*
|
||||
|
|
@ -634,7 +643,6 @@ static ha_config_t g_startup_cfg = {
|
|||
.max_promotion_retries = 5,
|
||||
.election_jitter_max_ms = 0,
|
||||
.election_priority_step_ms = 500,
|
||||
.committing_timeout_ms = 10000,
|
||||
};
|
||||
|
||||
static void test_nonblocking_single_node_election_sets_n_peers(void)
|
||||
|
|
@ -656,12 +664,13 @@ static void test_nonblocking_single_node_election_sets_n_peers(void)
|
|||
printf("test_nonblocking_single_node_election_sets_n_peers: PASS\n");
|
||||
}
|
||||
|
||||
static void test_nonblocking_single_node_election_enters_committing(void)
|
||||
static void test_nonblocking_single_node_election_enters_primary_directly(void)
|
||||
{
|
||||
/* Req 9.3, 8.1: After ha_start_election_async(n_peers=1) the role must be
|
||||
* CTRL_PRIMARY_COMMITTING, NOT CTRL_PRIMARY.
|
||||
/* Req 1.1, 9.3, 8.1: After ha_start_election_async(n_peers=1) the role must
|
||||
* be CTRL_PRIMARY directly — the election winner is the primary, with no
|
||||
* transitional leader sub-state (ha-single-leader-role, single leader role).
|
||||
* This validates that the new myapp_run path (without blocking for(;;) loop)
|
||||
* correctly transitions through the COMMITTING state. */
|
||||
* promotes straight to the primary role. */
|
||||
ha_state_t ha;
|
||||
ha_init(&ha, &g_startup_cfg, 0);
|
||||
ha.phase2_done = 0;
|
||||
|
|
@ -671,11 +680,11 @@ static void test_nonblocking_single_node_election_enters_committing(void)
|
|||
|
||||
ctrl_role_t role = (ctrl_role_t)atomic_load_explicit(
|
||||
&ha.role, memory_order_acquire);
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY_COMMITTING, (int)role,
|
||||
"myapp_run path: single-node must enter CTRL_PRIMARY_COMMITTING after election");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE((int)CTRL_PRIMARY, (int)role,
|
||||
"myapp_run path: single-node must enter CTRL_PRIMARY directly after election (Req 1.1)");
|
||||
|
||||
ha_fini(&ha);
|
||||
printf("test_nonblocking_single_node_election_enters_committing: PASS (role=%d)\n",
|
||||
printf("test_nonblocking_single_node_election_enters_primary_directly: PASS (role=%d)\n",
|
||||
(int)role);
|
||||
}
|
||||
|
||||
|
|
@ -810,13 +819,13 @@ void test_ha_startup(void)
|
|||
/* Req 1.1 — dispatch gate allows heartbeat in CTRL_INIT */
|
||||
RUN_TEST(test_dispatch_gate_init_allows_heartbeat);
|
||||
|
||||
/* Task 3.1 — dispatch gate: CTRL_PRIMARY_COMMITTING */
|
||||
RUN_TEST(test_dispatch_gate_committing_denies_job_ops);
|
||||
RUN_TEST(test_dispatch_gate_committing_allows_repl_ops);
|
||||
/* Task 3.1 — dispatch gate: primary without write quorum (single leader role) */
|
||||
RUN_TEST(test_dispatch_gate_primary_no_quorum_denies_job_ops);
|
||||
RUN_TEST(test_dispatch_gate_primary_no_quorum_allows_repl_ops);
|
||||
|
||||
/* Task 5.1 — myapp_run non-blocking startup sequence (Req 1.1, 9.3, 9.4) */
|
||||
RUN_TEST(test_nonblocking_single_node_election_sets_n_peers);
|
||||
RUN_TEST(test_nonblocking_single_node_election_enters_committing);
|
||||
RUN_TEST(test_nonblocking_single_node_election_enters_primary_directly);
|
||||
RUN_TEST(test_nonblocking_election_returns_in_under_10ms);
|
||||
|
||||
/* Task 1.1 (ha-heartbeat-liveness) — ha_init zero-initializes last_activity */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue