/* * test_repl_catchup.c — P2-5: standby catch-up state machine tests. * Tests repl_start_catchup, repl_complete_catchup, repl_needs_snapshot. * No live network I/O — pure state machine logic. */ #include #include #include #include #include #include #include "config.h" #include "repl.h" static void test_start_catchup_sets_in_catch_up(void) { replicator_t r; int ep = 0; repl_init(&r); repl_add_standby(&r, (void *)&ep, "sb", 7001); repl_start_catchup(&r, (void *)&ep, /*join_applied_lsn=*/5); /* in_catch_up must be set */ TEST_ASSERT_EQUAL_INT( 1, (int)atomic_load_explicit(&r.standbys[0].in_catch_up, memory_order_acquire)); /* join_applied_lsn must be stored */ TEST_ASSERT_EQUAL_UINT64(5, r.standbys[0].join_applied_lsn); repl_fini(&r); printf("test_start_catchup_sets_in_catch_up: PASS\n"); } static void test_complete_catchup_clears_in_catch_up(void) { replicator_t r; int ep = 0; repl_init(&r); repl_add_standby(&r, (void *)&ep, "sb", 7001); repl_start_catchup(&r, (void *)&ep, 5); repl_complete_catchup(&r, (void *)&ep); TEST_ASSERT_EQUAL_INT( 0, (int)atomic_load_explicit(&r.standbys[0].in_catch_up, memory_order_acquire)); repl_fini(&r); printf("test_complete_catchup_clears_in_catch_up: PASS\n"); } static void test_wait_ack_excludes_catching_up_standby(void) { replicator_t r; int ep = 0; repl_init(&r); repl_add_standby(&r, (void *)&ep, "sb", 7001); repl_start_catchup(&r, (void *)&ep, 5); /* mark as catching up */ /* repl_wait_ack must return -ENODEV (no non-catching-up standbys) */ int rc = repl_wait_ack(&r, 10, 50); TEST_ASSERT_EQUAL_INT(-ENODEV, rc); repl_fini(&r); printf("test_wait_ack_excludes_catching_up_standby: PASS\n"); } static void test_complete_catchup_allows_ack(void) { replicator_t r; int ep = 0; repl_init(&r); repl_add_standby(&r, (void *)&ep, "sb", 7001); repl_start_catchup(&r, (void *)&ep, 5); repl_complete_catchup(&r, (void *)&ep); /* After catch-up completes, the standby's ACK must count */ repl_on_ack(&r, (void *)&ep, 20); int rc = repl_wait_ack(&r, 10, 50); TEST_ASSERT_EQUAL_INT(0, rc); repl_fini(&r); printf("test_complete_catchup_allows_ack: PASS\n"); } static void test_repl_needs_snapshot_no_wal(void) { /* When standby LSN is 0 and no WAL exists, needs_snapshot returns 0 * (empty state = fresh standby, can start from empty — no snapshot needed) */ replicator_t r; repl_init(&r); int needs = repl_needs_snapshot(&r, /*standby_lsn=*/0, /*oldest_wal_lsn=*/0); TEST_ASSERT_EQUAL_INT(0, needs); repl_fini(&r); printf("test_repl_needs_snapshot_no_wal: PASS\n"); } static void test_repl_needs_snapshot_standby_behind_wal(void) { /* Standby LSN 5, oldest WAL segment starts at LSN 10 — snapshot required */ replicator_t r; repl_init(&r); int needs = repl_needs_snapshot(&r, /*standby_lsn=*/5, /*oldest_wal_lsn=*/10); TEST_ASSERT_EQUAL_INT(1, needs); repl_fini(&r); printf("test_repl_needs_snapshot_standby_behind_wal: PASS\n"); } static void test_repl_needs_snapshot_standby_within_wal(void) { /* Standby LSN 15, oldest WAL starts at LSN 10 — WAL replay sufficient */ replicator_t r; repl_init(&r); int needs = repl_needs_snapshot(&r, /*standby_lsn=*/15, /*oldest_wal_lsn=*/10); TEST_ASSERT_EQUAL_INT(0, needs); repl_fini(&r); printf("test_repl_needs_snapshot_standby_within_wal: PASS\n"); } void test_repl_catchup(void) { RUN_TEST(test_start_catchup_sets_in_catch_up); RUN_TEST(test_complete_catchup_clears_in_catch_up); RUN_TEST(test_wait_ack_excludes_catching_up_standby); RUN_TEST(test_complete_catchup_allows_ack); RUN_TEST(test_repl_needs_snapshot_no_wal); RUN_TEST(test_repl_needs_snapshot_standby_behind_wal); RUN_TEST(test_repl_needs_snapshot_standby_within_wal); }