test(raft-sim-conformance): focused Log Matching checker test for R3.2 (task 3.2)

This commit is contained in:
Rongsong Shen 2026-06-24 12:35:36 +00:00
parent 25b14a6dca
commit d9b5fd51e0
2 changed files with 72 additions and 1 deletions

View file

@ -63,7 +63,7 @@
- _Boundary: raft_sim.c, test_raft.c_
- _Depends: 1.1_
- [ ] 3.2 RED+GREEN: Log Matching checker (P)
- [x] 3.2 RED+GREEN: Log Matching checker (P)
- Test: two logs sharing `(idx, term)` but differing earlier fail; identical-prefix logs pass
- Implement `raft_sim_check_log_matching` (pairwise; shared `(idx, term)` ⇒ identical `1..idx`)
- Observable completion: both directions assert correctly

View file

@ -3622,6 +3622,76 @@ static void test_sim_check_log_matching_fail_on_divergence(void)
TEST_ASSERT_EQUAL_INT(0, raft_sim_check_log_matching(&c));
}
/*
* test_sim_3_2_log_matching_shared_idx_term_requires_identical_prefix (task 3.2)
*
* Spec R3.2 (Log Matching): "for any two nodes whose logs contain an entry at
* the same index with the same term, all entries at and before that index are
* identical (same term and value) across those two logs". This test exercises
* raft_sim_check_log_matching over hand-built clusters in BOTH directions, with
* the shared (idx, term) at a LATER index (3) and the divergence at an EARLIER
* index (1) — a distinct shape from the task-8.3 tests (which share index 2 and
* diverge at index 1) so the spec's R3.2 traceability is explicit.
*
* It is a pure checker unit test: roles/terms/bytes are set directly (no
* protocol drive) per Req 3.6 (observable per-node log state only). The checker
* pre-exists (raft-log-replication); this test confirms — and guards — that it
* correctly flags an earlier-prefix divergence and passes identical prefixes.
*/
static void test_sim_3_2_log_matching_shared_idx_term_requires_identical_prefix(void)
{
/* ---- Direction A: shared (idx=3, term=9), differ at earlier idx=1 ⇒ FAIL.
* Both nodes carry the SAME (term, value) at indices 2 and 3, and the SAME
* term at index 1, but index-1 VALUE differs. Because index 3 is shared with
* the same term, the prefix 1..3 must be identical — index 1 breaks it. */
{
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, raft_sim_init(&c, 3, 2000));
/* node 0: idx1=(t3,"AAA") idx2=(t6,"MMM") idx3=(t9,"ZZZ") */
c.nodes[0].log[0].term = 3; memcpy(c.nodes[0].log[0].bytes, "AAA", 3); c.nodes[0].log[0].len = 3;
c.nodes[0].log[1].term = 6; memcpy(c.nodes[0].log[1].bytes, "MMM", 3); c.nodes[0].log[1].len = 3;
c.nodes[0].log[2].term = 9; memcpy(c.nodes[0].log[2].bytes, "ZZZ", 3); c.nodes[0].log[2].len = 3;
c.nodes[0].log_count = 3;
/* node 1: SAME (idx3,t9) and (idx2,t6) but idx1 term matches with a
* DIFFERENT value ("CCC") — earlier-prefix divergence under a shared tip. */
c.nodes[1].log[0].term = 3; memcpy(c.nodes[1].log[0].bytes, "CCC", 3); c.nodes[1].log[0].len = 3;
c.nodes[1].log[1].term = 6; memcpy(c.nodes[1].log[1].bytes, "MMM", 3); c.nodes[1].log[1].len = 3;
c.nodes[1].log[2].term = 9; memcpy(c.nodes[1].log[2].bytes, "ZZZ", 3); c.nodes[1].log[2].len = 3;
c.nodes[1].log_count = 3;
TEST_ASSERT_EQUAL_INT(0, raft_sim_check_log_matching(&c));
}
/* ---- Direction B: identical prefixes ⇒ PASS. node 1 is a strict prefix of
* node 0 (idx 1..2 byte-identical); node 2 holds the full identical prefix. */
{
raft_sim_cluster_t c;
memset(&c, 0, sizeof(c));
TEST_ASSERT_EQUAL_INT(0, raft_sim_init(&c, 3, 2000));
c.nodes[0].log[0].term = 3; memcpy(c.nodes[0].log[0].bytes, "AAA", 3); c.nodes[0].log[0].len = 3;
c.nodes[0].log[1].term = 6; memcpy(c.nodes[0].log[1].bytes, "MMM", 3); c.nodes[0].log[1].len = 3;
c.nodes[0].log[2].term = 9; memcpy(c.nodes[0].log[2].bytes, "ZZZ", 3); c.nodes[0].log[2].len = 3;
c.nodes[0].log_count = 3;
/* node 1: shorter but byte-identical prefix (idx 1..2). */
c.nodes[1].log[0].term = 3; memcpy(c.nodes[1].log[0].bytes, "AAA", 3); c.nodes[1].log[0].len = 3;
c.nodes[1].log[1].term = 6; memcpy(c.nodes[1].log[1].bytes, "MMM", 3); c.nodes[1].log[1].len = 3;
c.nodes[1].log_count = 2;
/* node 2: full identical prefix (idx 1..3). */
c.nodes[2].log[0].term = 3; memcpy(c.nodes[2].log[0].bytes, "AAA", 3); c.nodes[2].log[0].len = 3;
c.nodes[2].log[1].term = 6; memcpy(c.nodes[2].log[1].bytes, "MMM", 3); c.nodes[2].log[1].len = 3;
c.nodes[2].log[2].term = 9; memcpy(c.nodes[2].log[2].bytes, "ZZZ", 3); c.nodes[2].log[2].len = 3;
c.nodes[2].log_count = 3;
TEST_ASSERT_EQUAL_INT(1, raft_sim_check_log_matching(&c));
}
}
/*
* test_sim_check_election_safety_fail_on_two_leaders (task 3.1)
*
@ -7412,6 +7482,7 @@ void test_raft(void)
RUN_TEST(test_sim_deliver_append_entries_replicates);
RUN_TEST(test_sim_check_log_matching_pass_on_converged);
RUN_TEST(test_sim_check_log_matching_fail_on_divergence);
RUN_TEST(test_sim_3_2_log_matching_shared_idx_term_requires_identical_prefix);
RUN_TEST(test_sim_check_election_safety_fail_on_two_leaders);
RUN_TEST(test_sim_check_election_safety_pass_on_single_leader);
RUN_TEST(test_sim_9_1_replication_and_partition_catch_up);