From d9b5fd51e0a5aeaba7349489610443705d7cd298 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Wed, 24 Jun 2026 12:35:36 +0000 Subject: [PATCH] test(raft-sim-conformance): focused Log Matching checker test for R3.2 (task 3.2) --- .kiro/specs/raft-sim-conformance/tasks.md | 2 +- src/lib/testsuite/test_raft.c | 71 +++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/.kiro/specs/raft-sim-conformance/tasks.md b/.kiro/specs/raft-sim-conformance/tasks.md index 46c259e7..607f758a 100644 --- a/.kiro/specs/raft-sim-conformance/tasks.md +++ b/.kiro/specs/raft-sim-conformance/tasks.md @@ -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 diff --git a/src/lib/testsuite/test_raft.c b/src/lib/testsuite/test_raft.c index 218d9710..1d6016cd 100644 --- a/src/lib/testsuite/test_raft.c +++ b/src/lib/testsuite/test_raft.c @@ -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);