feat(raft-library): reject even n_peers in raft_init; add test_raft_init_even_peers_returns_einval

This commit is contained in:
Rongsong Shen 2026-05-31 08:48:30 +00:00
parent fd9933c9d0
commit becf6ee765
2 changed files with 48 additions and 6 deletions

View file

@ -15,6 +15,11 @@ int raft_init(raft_state_t *s, const raft_config_t *cfg)
/* Validate required callbacks and n_peers before touching *s */
if (cfg->n_peers < 1)
return -EINVAL;
/* Even cluster sizes (>1) have no fault-tolerance advantage over the next
* smaller odd size and can mislead operators into thinking split-brain is
* impossible. Reject to prevent misconfiguration. */
if (cfg->n_peers > 1 && cfg->n_peers % 2 == 0)
return -EINVAL;
if (!cfg->cbs)
return -EINVAL;
if (!cfg->cbs->on_send_vote_request)

View file

@ -258,7 +258,7 @@ static void test_raft_init_null_become_leader_returns_einval(void)
cbs.on_become_leader = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 2; cfg.self_idx = 0; cfg.cbs = &cbs;
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
@ -273,7 +273,7 @@ static void test_raft_init_null_become_follower_returns_einval(void)
cbs.on_become_follower = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 2; cfg.self_idx = 0; cfg.cbs = &cbs;
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
@ -288,7 +288,7 @@ static void test_raft_init_null_persist_returns_einval(void)
cbs.on_persist = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 2; cfg.self_idx = 0; cfg.cbs = &cbs;
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
@ -303,7 +303,7 @@ static void test_raft_init_null_schedule_timeout_returns_einval(void)
cbs.on_schedule_timeout = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 2; cfg.self_idx = 0; cfg.cbs = &cbs;
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
@ -318,7 +318,7 @@ static void test_raft_init_null_cancel_timeout_returns_einval(void)
cbs.on_cancel_timeout = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 2; cfg.self_idx = 0; cfg.cbs = &cbs;
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
@ -333,11 +333,47 @@ static void test_raft_init_null_start_election_after_delay_returns_einval(void)
cbs.on_start_election_after_delay = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 2; cfg.self_idx = 0; cfg.cbs = &cbs;
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
/* n_peers even (>1) → -EINVAL: even-sized clusters have no fault-tolerance
* advantage over the next-smaller odd cluster; reject to prevent misconfiguration. */
static void test_raft_init_even_peers_returns_einval(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0xAB, sizeof(s));
/* n_peers=2: even, >1 → must fail */
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 2;
cfg.self_idx = 0;
cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
/* n_peers=4: even, >1 → must fail */
cfg.n_peers = 4;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
/* n_peers=1: single-node is always valid (odd by convention) */
cfg.n_peers = 1;
TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg));
/* n_peers=3: odd, >1 → must succeed */
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3;
cfg.self_idx = 0;
cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg));
/* s must NOT be unchanged after valid inits above */
TEST_ASSERT_EQUAL_INT(RAFT_FOLLOWER, s.role);
}
/* n_peers == 0 → -EINVAL */
static void test_raft_init_zero_peers_returns_einval(void)
{
@ -2849,6 +2885,7 @@ void test_raft(void)
RUN_TEST(test_raft_init_null_schedule_timeout_returns_einval);
RUN_TEST(test_raft_init_null_cancel_timeout_returns_einval);
RUN_TEST(test_raft_init_null_start_election_after_delay_returns_einval);
RUN_TEST(test_raft_init_even_peers_returns_einval);
RUN_TEST(test_raft_init_zero_peers_returns_einval);
RUN_TEST(test_raft_init_success_fields);
RUN_TEST(test_raft_init_default_election_timeout);