feat(ha-repl-hardening): snapshot transfer integration and dispatch-path tests (task 3.5d)

- Add ha_repl_send_snapshot_hook seam in ha.c/ha.h for XCE_TESTING
- test_dispatch_repl_action_send_snapshot: verifies SEND_SNAPSHOT
  decision triggers hook when standby_lsn < oldest_wal_lsn
- test_snapshot_recv_two_chunks_assembles_and_renames: verifies standby
  assembles snap.part across chunks and renames on isLast=true
- test_snapshot_recv_reconnect_discards_snappart: verifies O_TRUNC
  semantics on chunk_index==0 restart after reconnect
- All tests pass under ASAN
This commit is contained in:
Rongsong Shen 2026-06-05 02:13:11 +00:00
parent cbd91fca43
commit 18ad3e9c9f
5 changed files with 207 additions and 2 deletions

View file

@ -480,6 +480,73 @@ static void test_dispatch_repl_action_no_push_when_converged(void)
#endif
}
/* ── Task 3.5d: ha_dispatch_repl_action_for_standby SEND_SNAPSHOT wiring ────
*
* When standby_lsn < oldest_wal_lsn (standby predates WAL retention window),
* ha_dispatch_repl_action_for_standby must call ha_send_snapshot_to_standby
* (via ha_repl_send_snapshot_hook in XCE_TESTING builds).
*
* Requirements: 4.1 (ha-repl-hardening), design §Snapshot Transfer
*/
#ifdef XCE_TESTING
extern int (*ha_repl_send_snapshot_hook)(int peer_idx);
#endif
/* Call-counting state for the snapshot hook */
static int g_send_snapshot_count = 0;
static int g_send_snapshot_idx = -1;
static int stub_repl_send_snapshot(int peer_idx)
{
g_send_snapshot_count++;
g_send_snapshot_idx = peer_idx;
return 0;
}
/*
* Test: when standby_lsn < oldest_wal_lsn (and standby_tag == committed_tag),
* ha_dispatch_repl_action_for_standby must call ha_send_snapshot_to_standby
* (via hook in test mode).
*
* Steps:
* 1. Set ha->standby_reported_tag[0] = committed_tag (tag is current)
* 2. Set ha->standby_reported_lsn[0] = 10 (behind oldest_wal_lsn=50)
* 3. Call ha_dispatch_repl_action_for_standby(..., oldest_wal_lsn=50)
* 4. Assert hook called once with peer_idx=0
*
* Requirements: 4.1 (snapshot when standby_lsn < oldest_wal_lsn)
*/
static void test_dispatch_repl_action_send_snapshot(void)
{
#ifdef XCE_TESTING
ha_repl_send_snapshot_hook = stub_repl_send_snapshot;
g_send_snapshot_count = 0;
g_send_snapshot_idx = -1;
ha_state_t *ha = build_ha_for_convergence_test();
/* standby[0] has current config tag but LSN predates WAL window */
ha->standby_reported_tag[0] = 5;
ha->standby_reported_lsn[0] = 10; /* < oldest_wal_lsn=50 */
int rc = ha_dispatch_repl_action_for_standby(
ha, /*c=*/NULL, /*ep=*/NULL, /*peer_idx=*/0,
/*committed_tag=*/5, /*committed_lsn=*/100, /*oldest_wal_lsn=*/50);
(void)rc;
TEST_ASSERT_EQUAL_INT_MESSAGE(1, g_send_snapshot_count,
"SEND_SNAPSHOT: hook must fire once when standby_lsn < oldest_wal_lsn");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, g_send_snapshot_idx,
"SEND_SNAPSHOT: hook must receive correct peer_idx=0");
ha_fini(ha);
ha_repl_send_snapshot_hook = NULL;
printf("test_dispatch_repl_action_send_snapshot: PASS\n");
#else
printf("test_dispatch_repl_action_send_snapshot: SKIP (non-test build)\n");
#endif
}
/* ── Task 3.2: ha_evaluate_repl_action unified decision tree ─────────────── */
/*
@ -597,6 +664,8 @@ void test_ha_role(void)
/* Task 3.3: ha_dispatch_repl_action_for_standby PUSH_CONFIG wiring */
RUN_TEST(test_dispatch_repl_action_push_config_when_tag_behind);
RUN_TEST(test_dispatch_repl_action_no_push_when_converged);
/* Task 3.5d: ha_dispatch_repl_action_for_standby SEND_SNAPSHOT wiring */
RUN_TEST(test_dispatch_repl_action_send_snapshot);
}
/* test_am_i_primary suite deleted — am_i_primary() removed (election-iomgr-refactor) */