forked from lei.chen/myxce
feat(raft-commit-safety): (lastLogTerm,lastLogIndex) vote+prevote freshness (tasks 1.3, 4.1-4.2, 5.1-5.2, 6.2)
logOk (raft.tla:285, closes G6): local_last_log_term = log_len>0 ? on_get_entry_term(log_len):0; logOk = cand_last_log_term > local || (== && cand_last_log_index >= log_len). Lexicographic: term dominates, tie decided by index. Shared raft_log_ok helper. Vote (4.2): logOk deny inserted after stale-term/UpdateTerm/role-gate/one-vote-per-term and BEFORE on_vote_eligible (denies even when the app gate would grant); persist-before-grant intact. PreVote (5.2): identical logOk, fully side-effect-free (no term/vote/role/commit mutation, no on_persist). Signatures extended (1.3) on raft_on_vote_request/pre_vote_request; all 39 lib call sites + sim routing updated; sim captures the candidate's send-time last-log tuple (raft_sim_rpc_t). Empty-log call sites pass (0,0) → logOk true → existing outcomes preserved. 9 freshness tests. NOTE: controller cluster.c vote callers still use the old signatures and will NOT compile — an accepted, design-documented revalidation trigger; the controller adapter plumbing is owned by the downstream ha-term-safety spec (to be implemented next). Library suite ASAN green (239 tests).
This commit is contained in:
parent
195f660f9a
commit
56485e2fa3
5 changed files with 449 additions and 62 deletions
|
|
@ -130,6 +130,13 @@ typedef struct {
|
|||
int vote_eligible_return_value; /* configurable: 1=grant, 0=deny; default 1 */
|
||||
int vote_eligible_call_count;
|
||||
|
||||
/* on_get_entry_term mock (raft-commit-safety freshness, task 4.1/5.1).
|
||||
* The voter's local last-log term is returned for index == get_entry_term_log_len;
|
||||
* any other index returns 0 (absent). Default 0 keeps empty-log scenarios at the
|
||||
* pre-existing local_last_log_term == 0. */
|
||||
uint64_t local_last_log_term; /* term reported at index == get_entry_term_log_len */
|
||||
uint64_t get_entry_term_log_len; /* the index treated as the local last log */
|
||||
|
||||
/* on_trace mock */
|
||||
int trace_call_count;
|
||||
char last_trace_event[64];
|
||||
|
|
@ -241,6 +248,19 @@ static int mock_vote_eligible(void *ctx, int from_idx, uint64_t term)
|
|||
return g_mock.vote_eligible_return_value;
|
||||
}
|
||||
|
||||
/* Configurable local-log term reporter for freshness tests (task 4.1/5.1).
|
||||
* Returns g_mock.local_last_log_term for the configured last-log index, 0
|
||||
* otherwise. Defaults (both 0) reproduce the previous always-0 behaviour, so
|
||||
* empty-log call sites are unaffected. */
|
||||
static uint64_t mock_get_entry_term(void *ctx, uint64_t index)
|
||||
{
|
||||
(void)ctx;
|
||||
if (g_mock.get_entry_term_log_len != 0 &&
|
||||
index == g_mock.get_entry_term_log_len)
|
||||
return g_mock.local_last_log_term;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void mock_trace(void *ctx, const char *event, const char *detail)
|
||||
{
|
||||
(void)ctx; (void)detail;
|
||||
|
|
@ -940,7 +960,7 @@ static void test_vote_req_stale_term(void)
|
|||
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
|
||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 3, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 3, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -981,7 +1001,7 @@ static void test_vote_req_higher_term_updates_state(void)
|
|||
s.voted_for = 0; /* voted for self */
|
||||
s.voted_for_term = 2;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
|
||||
/* Term upgrade must happen: term updated, role stepped down */
|
||||
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||
|
|
@ -1021,7 +1041,7 @@ static void test_vote_req_same_term_already_voted_different(void)
|
|||
s.voted_for = 1;
|
||||
s.voted_for_term = 5;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 2, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 2, 5, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -1058,7 +1078,7 @@ static void test_vote_req_same_term_idempotent_same_candidate(void)
|
|||
s.voted_for = 1;
|
||||
s.voted_for_term = 5;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -1089,7 +1109,7 @@ static void test_vote_req_leader_denies(void)
|
|||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
s.role = RAFT_LEADER;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -1124,7 +1144,7 @@ static void test_vote_req_on_vote_eligible_denies(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -1161,7 +1181,7 @@ static void test_vote_req_on_vote_eligible_grants(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -1198,7 +1218,7 @@ static void test_vote_req_on_vote_eligible_null(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -1228,7 +1248,7 @@ static void test_vote_req_persist_fails(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -1257,14 +1277,14 @@ static void test_vote_req_reply_term_always_set(void)
|
|||
reset_mock();
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 7));
|
||||
reply_term = 0;
|
||||
raft_on_vote_request(&s, 1, 3, &reply_term);
|
||||
raft_on_vote_request(&s, 1, 3, 0, 0, &reply_term);
|
||||
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal current term=7 */
|
||||
|
||||
/* Path 2: term upgrade + grant */
|
||||
reset_mock();
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 3));
|
||||
reply_term = 0;
|
||||
raft_on_vote_request(&s, 1, 7, &reply_term);
|
||||
raft_on_vote_request(&s, 1, 7, 0, 0, &reply_term);
|
||||
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal upgraded term=7 */
|
||||
|
||||
/* Path 3: already voted for different — deny */
|
||||
|
|
@ -1272,7 +1292,7 @@ static void test_vote_req_reply_term_always_set(void)
|
|||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
s.voted_for = 1; s.voted_for_term = 5;
|
||||
reply_term = 0;
|
||||
raft_on_vote_request(&s, 2, 5, &reply_term);
|
||||
raft_on_vote_request(&s, 2, 5, 0, 0, &reply_term);
|
||||
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal 5 */
|
||||
|
||||
/* Path 4: persist fails — deny */
|
||||
|
|
@ -1281,7 +1301,7 @@ static void test_vote_req_reply_term_always_set(void)
|
|||
g_mock.persist_return_value = -1;
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
reply_term = 0;
|
||||
raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
TEST_ASSERT_EQUAL_UINT64(s.term, reply_term); /* must equal 5 */
|
||||
}
|
||||
|
||||
|
|
@ -4098,7 +4118,7 @@ static void test_proto_A3_late_election_denied(void)
|
|||
|
||||
/* Now node 2 tries to request a vote from node 1 at the same term=1 */
|
||||
uint64_t reply_term = 0;
|
||||
int rc = raft_on_vote_request(&c.nodes[1].state, 2, 1, &reply_term);
|
||||
int rc = raft_on_vote_request(&c.nodes[1].state, 2, 1, 0, 0, &reply_term);
|
||||
|
||||
/* Node 1 must deny — already voted for node 0 this term */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
|
|
@ -4153,7 +4173,7 @@ static void test_proto_A5_leader_denies_vote_request(void)
|
|||
|
||||
/* Send a vote request to the leader at the same term */
|
||||
uint64_t reply_term = 0;
|
||||
int rc = raft_on_vote_request(&c.nodes[0].state, 1, term_before, &reply_term);
|
||||
int rc = raft_on_vote_request(&c.nodes[0].state, 1, term_before, 0, 0, &reply_term);
|
||||
|
||||
/* Must be denied */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
|
|
@ -4187,7 +4207,7 @@ static void test_proto_C1_eligible_denies(void)
|
|||
s.term = 1;
|
||||
s.voted_for = -1;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||
|
|
@ -4214,7 +4234,7 @@ static void test_proto_C2_eligible_grants(void)
|
|||
s.term = 1;
|
||||
s.voted_for = -1;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, s.voted_for);
|
||||
|
|
@ -4240,7 +4260,7 @@ static void test_proto_C3_eligible_null_grants(void)
|
|||
s.term = 1;
|
||||
s.voted_for = -1;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
/* vote_eligible not called (NULL) */
|
||||
|
|
@ -4267,7 +4287,7 @@ static void test_proto_C4_stale_term_no_eligible_call(void)
|
|||
s.term = 5;
|
||||
s.voted_for = -1;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 3, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 3, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
||||
|
|
@ -4295,7 +4315,7 @@ static void test_proto_C5_already_voted_no_eligible_call(void)
|
|||
s.voted_for = 1;
|
||||
s.voted_for_term = 3;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 2, 3, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 2, 3, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
||||
|
|
@ -4395,7 +4415,7 @@ static void test_proto_H6_restored_vote_blocks_second_vote(void)
|
|||
TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg));
|
||||
|
||||
/* Different candidate (idx=2) requests a vote in the SAME term 5 */
|
||||
int rc = raft_on_vote_request(&s, 2, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 2, 5, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -4405,7 +4425,7 @@ static void test_proto_H6_restored_vote_blocks_second_vote(void)
|
|||
TEST_ASSERT_EQUAL_UINT64(5, s.voted_for_term);
|
||||
|
||||
/* Same candidate (idx=1) retransmitting must still be re-granted */
|
||||
rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
}
|
||||
|
||||
|
|
@ -4445,7 +4465,7 @@ static void test_proto_H3_persist_before_vote_recorded(void)
|
|||
s.term = 1;
|
||||
s.voted_for = -1;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||
|
||||
/* Must grant */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
|
|
@ -4481,7 +4501,7 @@ static void test_proto_H4_persist_fail_denies(void)
|
|||
s.term = 1;
|
||||
s.voted_for = -1;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 1, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 1, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||
|
|
@ -4602,7 +4622,7 @@ static void test_proto_B2_higher_term_updates_state(void)
|
|||
s.voted_for = 1; /* node voted for itself in an earlier election */
|
||||
s.voted_for_term = 1; /* that vote was in term 1 */
|
||||
|
||||
int rc = raft_on_vote_request(&s, 0, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 0, 5, 0, 0, &reply_term);
|
||||
|
||||
/* Term must be upgraded to 5 */
|
||||
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||
|
|
@ -4632,7 +4652,7 @@ static void test_proto_B3_stale_term_denied(void)
|
|||
reset_mock();
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_vote_request(&s, 0, 3, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 0, 3, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
|
|
@ -4660,7 +4680,7 @@ static void test_proto_B4_different_candidate_denied(void)
|
|||
s.voted_for = 1;
|
||||
s.voted_for_term = 3;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 2, 3, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 2, 3, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(3, reply_term);
|
||||
|
|
@ -4691,7 +4711,7 @@ static void test_proto_B5_idempotent_same_candidate(void)
|
|||
s.voted_for = 1;
|
||||
s.voted_for_term = 3;
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 3, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 3, 0, 0, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(3, reply_term);
|
||||
|
|
@ -5055,7 +5075,7 @@ static void test_proto_F2_voted_during_delay_skip(void)
|
|||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||
|
||||
/* During the delay, a vote request arrives from node 0 at term=1 */
|
||||
int rc = raft_on_vote_request(&s, 0, 1, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 0, 1, 0, 0, &reply_term);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, s.voted_for);
|
||||
|
||||
|
|
@ -5220,6 +5240,279 @@ static void test_proto_G2_partition_minority_no_quorum(void)
|
|||
|
||||
#endif /* XCE_TESTING */
|
||||
|
||||
/* ---------- (lastLogTerm,lastLogIndex) vote freshness tests (task 4.1) ----------
|
||||
*
|
||||
* raft.tla HandleRequestVoteRequest logOk (285), closes G6. The library now
|
||||
* computes logOk itself from the candidate's advertised (cand_last_log_term,
|
||||
* cand_last_log_index) against its own (on_get_entry_term(log_len), log_len),
|
||||
* BEFORE the application gate. */
|
||||
|
||||
/* Configure the voter's local log: last index = log_len, last term = last_term. */
|
||||
static void set_voter_log(raft_state_t *s, uint64_t log_len, uint64_t last_term)
|
||||
{
|
||||
s->log_len = log_len;
|
||||
g_mock.get_entry_term_log_len = log_len;
|
||||
g_mock.local_last_log_term = last_term;
|
||||
}
|
||||
|
||||
/*
|
||||
* test_vote_req_freshness_stale_term_denied_higher_index
|
||||
* Voter log = (term 5, index 3). Candidate advertises a STALE last-log term (4)
|
||||
* but a HIGHER index (10). logOk false -> deny, even though the app gate grants.
|
||||
* (Req 3.1, 3.3, 3.4.)
|
||||
*/
|
||||
static void test_vote_req_freshness_stale_term_denied_higher_index(void)
|
||||
{
|
||||
raft_state_t s;
|
||||
raft_callbacks_t cbs = make_mock_cbs();
|
||||
uint64_t reply_term = 0;
|
||||
|
||||
cbs.on_trace = mock_trace;
|
||||
cbs.on_vote_eligible = mock_vote_eligible;
|
||||
cbs.on_get_entry_term = mock_get_entry_term;
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
set_voter_log(&s, 3, 5);
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 4, 10, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, reply_term);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||
}
|
||||
|
||||
/*
|
||||
* test_vote_req_freshness_fresher_term_granted_smaller_index
|
||||
* Voter log = (term 5, index 10). Candidate (term 6 fresher, index 2 smaller).
|
||||
* logOk = (6 > 5) -> grant. (Req 3.1, 3.6.)
|
||||
*/
|
||||
static void test_vote_req_freshness_fresher_term_granted_smaller_index(void)
|
||||
{
|
||||
raft_state_t s;
|
||||
raft_callbacks_t cbs = make_mock_cbs();
|
||||
uint64_t reply_term = 0;
|
||||
|
||||
cbs.on_trace = mock_trace;
|
||||
cbs.on_vote_eligible = mock_vote_eligible;
|
||||
cbs.on_get_entry_term = mock_get_entry_term;
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
set_voter_log(&s, 10, 5);
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 6, 2, &reply_term);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, g_mock.persist_count);
|
||||
TEST_ASSERT_EQUAL_INT(1, s.voted_for);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, s.voted_for_term);
|
||||
TEST_ASSERT_EQUAL_INT(1, g_mock.vote_eligible_call_count);
|
||||
}
|
||||
|
||||
/*
|
||||
* test_vote_req_freshness_tie_term_index_boundary
|
||||
* Voter log = (term 5, index 4). On a term TIE, grant requires cand index >=
|
||||
* voter log_len. index 3 (<4) -> deny; index 4 (==4) -> grant. (Req 3.1.)
|
||||
*/
|
||||
static void test_vote_req_freshness_tie_term_index_boundary(void)
|
||||
{
|
||||
raft_state_t s;
|
||||
raft_callbacks_t cbs = make_mock_cbs();
|
||||
uint64_t reply_term = 0;
|
||||
|
||||
cbs.on_vote_eligible = mock_vote_eligible;
|
||||
cbs.on_get_entry_term = mock_get_entry_term;
|
||||
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
set_voter_log(&s, 4, 5);
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 5, 3, &reply_term); /* idx 3 < 4 */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
set_voter_log(&s, 4, 5);
|
||||
rc = raft_on_vote_request(&s, 1, 5, 5, 4, &reply_term); /* idx 4 == 4 */
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, g_mock.persist_count);
|
||||
}
|
||||
|
||||
/*
|
||||
* test_vote_req_freshness_empty_voter_log_term_zero
|
||||
* Empty voter log (log_len 0): local last-log term 0. Candidate (0,0) satisfies
|
||||
* logOk = (0>0)||(0==0 && 0>=0) = true -> grant. (Req 3.1.)
|
||||
*/
|
||||
static void test_vote_req_freshness_empty_voter_log_term_zero(void)
|
||||
{
|
||||
raft_state_t s;
|
||||
raft_callbacks_t cbs = make_mock_cbs();
|
||||
uint64_t reply_term = 0;
|
||||
|
||||
cbs.on_vote_eligible = mock_vote_eligible;
|
||||
cbs.on_get_entry_term = mock_get_entry_term;
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, g_mock.persist_count);
|
||||
}
|
||||
|
||||
/*
|
||||
* test_vote_req_freshness_logok_precedes_app_gate
|
||||
* !logOk denies even when on_vote_eligible WOULD grant, and the app gate is not
|
||||
* consulted (logOk strictly precedes it). (Req 3.4.)
|
||||
*/
|
||||
static void test_vote_req_freshness_logok_precedes_app_gate(void)
|
||||
{
|
||||
raft_state_t s;
|
||||
raft_callbacks_t cbs = make_mock_cbs();
|
||||
uint64_t reply_term = 0;
|
||||
|
||||
cbs.on_vote_eligible = mock_vote_eligible;
|
||||
cbs.on_get_entry_term = mock_get_entry_term;
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
set_voter_log(&s, 8, 7);
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 7, 1, &reply_term);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.vote_eligible_call_count);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||
}
|
||||
|
||||
/* ---------- (lastLogTerm,lastLogIndex) PreVote freshness tests (task 5.1) ----------
|
||||
*
|
||||
* The PreVote path applies the IDENTICAL logOk, and mutates NOTHING. (Req 4.1-4.5.) */
|
||||
|
||||
/*
|
||||
* test_pre_vote_req_freshness_stale_tuple_denied
|
||||
* Voter (term 5, index 3). PreVote tuple (term 4 stale, index 10) -> deny, no
|
||||
* mutation, no persist. (Req 4.1, 4.3.)
|
||||
*/
|
||||
static void test_pre_vote_req_freshness_stale_tuple_denied(void)
|
||||
{
|
||||
raft_state_t s;
|
||||
raft_callbacks_t cbs = make_mock_cbs();
|
||||
|
||||
cbs.on_vote_eligible = mock_vote_eligible;
|
||||
cbs.on_get_entry_term = mock_get_entry_term;
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
set_voter_log(&s, 3, 5);
|
||||
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 5, 4, 10);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||
TEST_ASSERT_EQUAL_UINT64(0, s.voted_for_term);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
|
||||
TEST_ASSERT_EQUAL_UINT64(0, s.commit_index);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||
}
|
||||
|
||||
/*
|
||||
* test_pre_vote_req_freshness_fresher_tuple_granted
|
||||
* Voter (term 5, index 10). PreVote (term 6 fresher, index 2) -> grant, still
|
||||
* side-effect-free. (Req 4.1.)
|
||||
*/
|
||||
static void test_pre_vote_req_freshness_fresher_tuple_granted(void)
|
||||
{
|
||||
raft_state_t s;
|
||||
raft_callbacks_t cbs = make_mock_cbs();
|
||||
|
||||
cbs.on_vote_eligible = mock_vote_eligible;
|
||||
cbs.on_get_entry_term = mock_get_entry_term;
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
set_voter_log(&s, 10, 5);
|
||||
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 5, 6, 2);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||
}
|
||||
|
||||
/*
|
||||
* test_pre_vote_req_freshness_no_mutation_grant_and_deny
|
||||
* Across BOTH a grant and a deny, no change to term/voted_for/voted_for_term/
|
||||
* role/commit_index and no on_persist. (Req 4.4.)
|
||||
*/
|
||||
static void test_pre_vote_req_freshness_no_mutation_grant_and_deny(void)
|
||||
{
|
||||
raft_state_t s;
|
||||
raft_callbacks_t cbs = make_mock_cbs();
|
||||
|
||||
cbs.on_vote_eligible = mock_vote_eligible;
|
||||
cbs.on_get_entry_term = mock_get_entry_term;
|
||||
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
s.commit_index = 2;
|
||||
set_voter_log(&s, 4, 5);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, raft_on_pre_vote_request(&s, 1, 5, 5, 9));
|
||||
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||
TEST_ASSERT_EQUAL_UINT64(0, s.voted_for_term);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
|
||||
TEST_ASSERT_EQUAL_UINT64(2, s.commit_index);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
s.commit_index = 2;
|
||||
set_voter_log(&s, 4, 5);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, raft_on_pre_vote_request(&s, 1, 5, 5, 1));
|
||||
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||
TEST_ASSERT_EQUAL_INT(-1, s.voted_for);
|
||||
TEST_ASSERT_EQUAL_UINT64(0, s.voted_for_term);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
|
||||
TEST_ASSERT_EQUAL_UINT64(2, s.commit_index);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||
}
|
||||
|
||||
/*
|
||||
* test_pre_vote_req_freshness_stale_term_still_denied
|
||||
* The pre-existing stale-term deny stays in force even with a fresh tuple:
|
||||
* cand_term < s.term denies. (Req 4.5.)
|
||||
*/
|
||||
static void test_pre_vote_req_freshness_stale_term_still_denied(void)
|
||||
{
|
||||
raft_state_t s;
|
||||
raft_callbacks_t cbs = make_mock_cbs();
|
||||
|
||||
cbs.on_vote_eligible = mock_vote_eligible;
|
||||
cbs.on_get_entry_term = mock_get_entry_term;
|
||||
reset_mock();
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
set_voter_log(&s, 3, 5);
|
||||
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 4, 99, 99);
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_INT(0, g_mock.persist_count);
|
||||
}
|
||||
|
||||
/* ---------- raft_on_pre_vote_request tests (task 2.1) ---------- */
|
||||
|
||||
/*
|
||||
|
|
@ -5249,7 +5542,7 @@ static void test_pre_vote_req_grant_higher_term_eligible(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 6);
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 6, 0, 0);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
|
||||
|
|
@ -5283,7 +5576,7 @@ static void test_pre_vote_req_grant_equal_term_eligible(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 5); /* equal term */
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 5, 0, 0); /* equal term */
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
|
||||
|
|
@ -5312,7 +5605,7 @@ static void test_pre_vote_req_deny_stale_term(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 4); /* stale */
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 4, 0, 0); /* stale */
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
|
||||
|
|
@ -5346,7 +5639,7 @@ static void test_pre_vote_req_deny_eligibility_rejects(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 6);
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 6, 0, 0);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_DENY, rc);
|
||||
TEST_ASSERT_EQUAL_INT(1, g_mock.vote_eligible_call_count);
|
||||
|
|
@ -5375,7 +5668,7 @@ static void test_pre_vote_req_null_eligible_grants(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, init_vote_node(&s, &cbs, 3, 0, 5));
|
||||
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 6);
|
||||
int rc = raft_on_pre_vote_request(&s, 1, 6, 0, 0);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
|
||||
|
|
@ -5409,7 +5702,7 @@ static void test_pre_vote_req_grant_preserves_existing_vote(void)
|
|||
s.voted_for_term = 5;
|
||||
|
||||
/* A different candidate (peer 2) probes a PreVote for term 6. */
|
||||
int rc = raft_on_pre_vote_request(&s, 2, 6);
|
||||
int rc = raft_on_pre_vote_request(&s, 2, 6, 0, 0);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(RAFT_GRANT, rc);
|
||||
|
||||
|
|
@ -5940,14 +6233,14 @@ static void test_invariant_prevote_sequence_pure_no_persist(void)
|
|||
TEST_ASSERT_EQUAL_INT(0, raft_begin_pre_vote(&s));
|
||||
|
||||
/* (b) answer an incoming PreVote request that PASSES eligibility — grant */
|
||||
TEST_ASSERT_EQUAL_INT(1, raft_on_pre_vote_request(&s, 2, 6 /* higher */));
|
||||
TEST_ASSERT_EQUAL_INT(1, raft_on_pre_vote_request(&s, 2, 6 /* higher */, 0, 0));
|
||||
|
||||
/* (c) answer a STALE-term incoming request — deny */
|
||||
TEST_ASSERT_EQUAL_INT(0, raft_on_pre_vote_request(&s, 2, 3 /* stale */));
|
||||
TEST_ASSERT_EQUAL_INT(0, raft_on_pre_vote_request(&s, 2, 3 /* stale */, 0, 0));
|
||||
|
||||
/* (d) answer an incoming request the eligibility gate REJECTS — deny */
|
||||
g_mock.vote_eligible_return_value = 0;
|
||||
TEST_ASSERT_EQUAL_INT(0, raft_on_pre_vote_request(&s, 2, 6));
|
||||
TEST_ASSERT_EQUAL_INT(0, raft_on_pre_vote_request(&s, 2, 6, 0, 0));
|
||||
g_mock.vote_eligible_return_value = 1;
|
||||
|
||||
/* (e) tally replies: a grant (drives to WON quorum on 3-node), then a
|
||||
|
|
@ -6103,7 +6396,7 @@ static void test_invariant_real_vote_higher_term_adopt_unchanged(void)
|
|||
s.voted_for_term = 2;
|
||||
reset_mock();
|
||||
|
||||
int rc = raft_on_vote_request(&s, 1, 5, &reply_term);
|
||||
int rc = raft_on_vote_request(&s, 1, 5, 0, 0, &reply_term);
|
||||
|
||||
/* Higher-term adopt + leader step-down, exactly as bug-339/340 require. */
|
||||
TEST_ASSERT_EQUAL_UINT64(5, s.term);
|
||||
|
|
@ -6229,6 +6522,19 @@ void test_raft(void)
|
|||
RUN_TEST(test_vote_req_persist_fails);
|
||||
RUN_TEST(test_vote_req_reply_term_always_set);
|
||||
|
||||
/* (lastLogTerm,lastLogIndex) binding-vote freshness (task 4.1, Req 3.1-3.6) */
|
||||
RUN_TEST(test_vote_req_freshness_stale_term_denied_higher_index);
|
||||
RUN_TEST(test_vote_req_freshness_fresher_term_granted_smaller_index);
|
||||
RUN_TEST(test_vote_req_freshness_tie_term_index_boundary);
|
||||
RUN_TEST(test_vote_req_freshness_empty_voter_log_term_zero);
|
||||
RUN_TEST(test_vote_req_freshness_logok_precedes_app_gate);
|
||||
|
||||
/* (lastLogTerm,lastLogIndex) PreVote freshness (task 5.1, Req 4.1-4.5) */
|
||||
RUN_TEST(test_pre_vote_req_freshness_stale_tuple_denied);
|
||||
RUN_TEST(test_pre_vote_req_freshness_fresher_tuple_granted);
|
||||
RUN_TEST(test_pre_vote_req_freshness_no_mutation_grant_and_deny);
|
||||
RUN_TEST(test_pre_vote_req_freshness_stale_term_still_denied);
|
||||
|
||||
/* raft_on_election_timeout, raft_on_leader_lost, raft_check_and_start_election (task 3.5) */
|
||||
RUN_TEST(test_election_timeout_undecided);
|
||||
RUN_TEST(test_election_timeout_already_decided);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue