feat(raft-log-replication): require 4 log callbacks in raft_init + sim log backing (tasks 2.1, 8.1)

2.1: raft_init returns -EINVAL when any of on_persist_entry/on_get_entry_term/
on_send_append_entries/on_truncate_from is NULL (Req 7.1-7.4), before any write
to *s; restores s->log_len = cfg->initial_log_len (Req 1.1).

8.1: raft_sim gains a per-node in-memory log (term+bytes per 1-based index) and
an AE buffer queue; wires all four callbacks (persist/get-term/truncate/send).
Pulled forward to land with 2.1 so the stricter init does not leave the
sim/proto suite red between tasks. AE delivery deferred to 8.2.

Suite green under ASAN (Ok:1 Fail:0).
This commit is contained in:
Rongsong Shen 2026-06-21 15:30:09 +00:00
parent 7b6cbb8e3e
commit d70f2507c3
5 changed files with 314 additions and 2 deletions

View file

@ -46,6 +46,37 @@ static void stub_start_election_after_delay(void *ctx, uint32_t delay_ms)
(void)ctx; (void)delay_ms;
}
/* ---- log-replication callback stubs (raft-log-replication) ---- */
static int stub_persist_entry(void *ctx, uint64_t index, uint64_t term,
const void *entry, size_t len)
{
(void)ctx; (void)index; (void)term; (void)entry; (void)len;
return 0;
}
static uint64_t stub_get_entry_term(void *ctx, uint64_t index)
{
(void)ctx; (void)index;
return 0;
}
static int stub_send_append_entries(void *ctx, int peer_idx,
uint64_t prev_log_index,
uint64_t prev_log_term,
uint64_t leader_commit, int n_entries)
{
(void)ctx; (void)peer_idx; (void)prev_log_index; (void)prev_log_term;
(void)leader_commit; (void)n_entries;
return 0;
}
static int stub_truncate_from(void *ctx, uint64_t index)
{
(void)ctx; (void)index;
return 0;
}
/* Build a fully-populated callbacks struct */
static raft_callbacks_t make_full_cbs(void)
{
@ -58,6 +89,10 @@ static raft_callbacks_t make_full_cbs(void)
cbs.on_schedule_timeout = stub_schedule_timeout;
cbs.on_cancel_timeout = stub_cancel_timeout;
cbs.on_start_election_after_delay = stub_start_election_after_delay;
cbs.on_persist_entry = stub_persist_entry;
cbs.on_get_entry_term = stub_get_entry_term;
cbs.on_send_append_entries = stub_send_append_entries;
cbs.on_truncate_from = stub_truncate_from;
/* on_vote_eligible and on_trace left NULL (optional) */
return cbs;
}
@ -222,6 +257,12 @@ static raft_callbacks_t make_mock_cbs(void)
cbs.on_cancel_timeout = mock_cancel_timeout;
cbs.on_start_election_after_delay = mock_start_election_after_delay;
cbs.on_send_pre_vote_request = mock_send_pre_vote_request;
/* Log-replication callbacks: stubs (task 2.1 only needs them non-NULL for
* raft_init validation; behaviour is exercised by later tasks). */
cbs.on_persist_entry = stub_persist_entry;
cbs.on_get_entry_term = stub_get_entry_term;
cbs.on_send_append_entries = stub_send_append_entries;
cbs.on_truncate_from = stub_truncate_from;
/* on_vote_eligible and on_trace left NULL — tests that need them set explicitly */
return cbs;
}
@ -361,6 +402,86 @@ static void test_raft_init_null_start_election_after_delay_returns_einval(void)
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
/* ---- log-replication required callbacks (Req 7.1–7.4): each NULL → -EINVAL ---- */
static void test_raft_init_null_persist_entry_returns_einval(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0, sizeof(s));
cbs.on_persist_entry = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
static void test_raft_init_null_get_entry_term_returns_einval(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0, sizeof(s));
cbs.on_get_entry_term = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
static void test_raft_init_null_send_append_entries_returns_einval(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0, sizeof(s));
cbs.on_send_append_entries = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
static void test_raft_init_null_truncate_from_returns_einval(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0, sizeof(s));
cbs.on_truncate_from = NULL;
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3; cfg.self_idx = 0; cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(-EINVAL, raft_init(&s, &cfg));
}
/* All four log callbacks present → succeeds; initial_log_len restored to log_len (Req 1.1) */
static void test_raft_init_log_callbacks_present_succeeds(void)
{
raft_state_t s;
raft_callbacks_t cbs = make_full_cbs();
raft_config_t cfg;
memset(&s, 0, sizeof(s));
memset(&cfg, 0, sizeof(cfg));
cfg.n_peers = 3;
cfg.self_idx = 0;
cfg.initial_log_len = 5; /* WAL length restored from disk */
cfg.cbs = &cbs;
TEST_ASSERT_EQUAL_INT(0, raft_init(&s, &cfg));
TEST_ASSERT_EQUAL_UINT64(5, s.log_len);
}
/* 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)
@ -3864,6 +3985,11 @@ 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_null_persist_entry_returns_einval);
RUN_TEST(test_raft_init_null_get_entry_term_returns_einval);
RUN_TEST(test_raft_init_null_send_append_entries_returns_einval);
RUN_TEST(test_raft_init_null_truncate_from_returns_einval);
RUN_TEST(test_raft_init_log_callbacks_present_succeeds);
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);