myxce/src/controller/testsuite/test_repl_catchup.c
Rongsong Shen 6e4fd1aa81 feat: P2-5 repl_start/complete_catchup, repl_needs_snapshot — standby catch-up state
Implement the catch-up state machine as testable pure-logic functions:

- repl_start_catchup(): marks standby in_catch_up=1, stores join_applied_lsn
- repl_complete_catchup(): clears in_catch_up=0, broadcasts ack_cond so any
  blocked repl_wait_ack() re-checks the ACK condition immediately
- repl_needs_snapshot(): pure predicate — returns 1 when standby LSN predates
  oldest surviving WAL segment (snapshot transfer required), 0 otherwise

Also fixes pre-existing linker error: repl.c was missing from controller_src
(xce_controller production binary), causing undefined references to repl_*
symbols introduced in P2-4.

7 new TDD tests in test_repl_catchup.c — all pass. Existing tests green.
2026-05-07 18:14:12 +00:00

134 lines
4.1 KiB
C

/*
* 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 <errno.h>
#include <stdatomic.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unity/unity.h>
#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);
}