From b20f5558c26e9e9f2836d4d7792415c542ed9840 Mon Sep 17 00:00:00 2001
From: Shlok Kyal <shlok.kyal.oss@gmail.com>
Date: Wed, 12 Nov 2025 15:13:17 +0530
Subject: [PATCH v7 2/2] Add test for new stats for slot sync skip

---
 src/test/recovery/meson.build                 |   1 +
 .../recovery/t/050_slotsync_skip_stats.pl     | 198 ++++++++++++++++++
 2 files changed, 199 insertions(+)
 create mode 100644 src/test/recovery/t/050_slotsync_skip_stats.pl

diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 523a5cd5b52..17551cf114a 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -58,6 +58,7 @@ tests += {
       't/047_checkpoint_physical_slot.pl',
       't/048_vacuum_horizon_floor.pl',
       't/049_wait_for_lsn.pl',
+      't/050_slotsync_skip_stats.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/050_slotsync_skip_stats.pl b/src/test/recovery/t/050_slotsync_skip_stats.pl
new file mode 100644
index 00000000000..3f9d235c2b6
--- /dev/null
+++ b/src/test/recovery/t/050_slotsync_skip_stats.pl
@@ -0,0 +1,198 @@
+# Copyright (c) 2025, PostgreSQL Global Development Group
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Skip all tests if injection points are not supported in this build
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+# Initialize the primary cluster
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 'logical');
+$primary->append_conf(
+	'postgresql.conf', qq{
+autovacuum = off
+});
+$primary->start;
+
+# Check if the extension injection_points is available, as it may be
+# possible that this script is run with installcheck, where the module
+# would not be installed by default.
+if (!$primary->check_extension('injection_points'))
+{
+	plan skip_all => 'Extension injection_points not installed';
+}
+
+# Load the injection_points extension
+$primary->safe_psql('postgres', q(CREATE EXTENSION injection_points));
+
+# Take a backup of the primary for standby initialization
+my $backup_name = 'backup';
+$primary->backup($backup_name);
+
+# Initialize standby from primary backup
+my $standby = PostgreSQL::Test::Cluster->new('standby');
+$standby->init_from_backup($primary, $backup_name, has_streaming => 1);
+
+my $connstr = $primary->connstr;
+$standby->append_conf(
+	'postgresql.conf', qq(
+hot_standby_feedback = on
+primary_slot_name = 'sb1_slot'
+primary_conninfo = '$connstr dbname=postgres'
+));
+
+# Create a physical replication slot on primary for standby
+$primary->safe_psql('postgres',
+	q{SELECT pg_create_physical_replication_slot('sb1_slot');});
+
+$standby->start;
+
+# Create a logical replication slot on primary for testing
+$primary->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('slot_sync', 'test_decoding', false, false, true)"
+);
+
+# Wait for standby to catch up
+$primary->wait_for_replay_catchup($standby);
+
+# Initial sync of replication slots
+$standby->safe_psql('postgres', "SELECT pg_sync_replication_slots();");
+
+# Verify that initially there is no skip reason
+my $result = $standby->safe_psql(
+	'postgres',
+	"SELECT slotsync_skip_reason FROM pg_replication_slots
+     WHERE slot_name = 'slot_sync' AND synced"
+);
+is($result, 'none', "slot sync reason is none");
+
+# Update pg_hba.conf and restart the primary to reject streaming replication
+# connections. WAL records won't be replicated to the standby until the
+# configuration is restored.
+unlink($primary->data_dir . '/pg_hba.conf');
+$primary->append_conf(
+	'pg_hba.conf', qq{
+local   all             all                                     trust
+host    all             all             127.0.0.1/32            trust
+host    all             all             ::1/128                 trust
+});
+$primary->restart;
+
+# Advance the failover slot so that confirmed flush LSN of remote slot become
+# ahead of standby's flushed LSN
+$primary->safe_psql(
+	'postgres', qq(
+    CREATE TABLE t1(a int);
+    INSERT INTO t1 VALUES(1);
+	SELECT pg_replication_slot_advance('slot_sync', pg_current_wal_lsn());
+));
+
+my ($stdout, $stderr);
+# Attempt to sync replication slots while standby is behind
+($result, $stdout, $stderr) =
+  $standby->psql('postgres', "SELECT pg_sync_replication_slots();");
+
+# Verify pg_sync_replication_slots is failing
+ok( $stderr =~ /skipping slot synchronization because the received slot sync/,
+	'pg_sync_replication_slots failed as expected');
+
+# Check skip reason and count when standby is behind
+$result = $standby->safe_psql(
+	'postgres',
+	"SELECT slotsync_skip_reason FROM pg_replication_slots
+     WHERE slot_name = 'slot_sync' AND synced AND NOT temporary"
+);
+is($result, 'wal_not_flushed', "slot sync skip when standby is behind");
+
+$result = $standby->safe_psql('postgres',
+	"SELECT slotsync_skip_count FROM pg_stat_replication_slots WHERE slot_name = 'slot_sync'"
+);
+is($result, '1', "check slot sync skip count");
+
+# Repeat sync to ensure skip count increments
+($result, $stdout, $stderr) =
+  $standby->psql('postgres', "SELECT pg_sync_replication_slots();");
+
+$result = $standby->safe_psql('postgres',
+	"SELECT slotsync_skip_count FROM pg_stat_replication_slots WHERE slot_name = 'slot_sync'"
+);
+is($result, '2', "check slot sync skip count");
+
+# Restore streaming replication connection
+$primary->append_conf(
+	'pg_hba.conf', qq{
+local   replication     all                                     trust
+host    replication     all             127.0.0.1/32            trust
+host    replication     all             ::1/128                 trust
+});
+$primary->restart;
+
+# Wait for standby to catch up
+$primary->wait_for_replay_catchup($standby);
+
+# Check that skip reason is reset after successful sync
+$standby->safe_psql('postgres', "SELECT pg_sync_replication_slots();");
+$result = $standby->safe_psql(
+	'postgres',
+	"SELECT slotsync_skip_reason FROM pg_replication_slots
+     WHERE slot_name = 'slot_sync' AND synced AND NOT temporary"
+);
+is($result, 'none', "slotsync_skip_reason is reset after successful sync");
+
+# Cleanup: drop the logical slot and ensure standby catches up
+$primary->safe_psql('postgres',
+	"SELECT pg_drop_replication_slot('slot_sync')");
+$primary->wait_for_replay_catchup($standby);
+
+$standby->safe_psql('postgres', "SELECT pg_sync_replication_slots();");
+
+# Test for case when slot sync is skipped when the remote slot is
+# behind the local slot.
+$primary->safe_psql('postgres',
+	"SELECT pg_create_logical_replication_slot('slot_sync', 'test_decoding', false, false, true)"
+);
+
+# Attach injection point to simulate wait
+my $standby_psql = $standby->background_psql('postgres');
+$standby_psql->query_safe(
+	q(select injection_points_attach('slot-sync-skip','wait')));
+
+# Initiate sync of failover slots
+$standby_psql->query_until(
+	qr/slot_sync/,
+	q(
+\echo slot_sync
+select pg_sync_replication_slots();
+));
+
+# Wait for backend to reach injection point
+$standby->wait_for_event('client backend', 'slot-sync-skip');
+
+# Logical slot is temporary and sync will skip because remote is behind
+$result = $standby->safe_psql(
+	'postgres',
+	"SELECT slotsync_skip_reason FROM pg_replication_slots
+     WHERE slot_name = 'slot_sync' AND synced AND temporary"
+);
+is($result, 'remote_behind', "slot sync skip as remote is behind");
+
+$result = $standby->safe_psql('postgres',
+	"SELECT slotsync_skip_count FROM pg_stat_replication_slots WHERE slot_name = 'slot_sync'"
+);
+is($result, '1', "check slot sync skip count");
+
+# Detach injection point
+$standby->safe_psql(
+	'postgres', q{
+	SELECT injection_points_detach('slot-sync-skip');
+	SELECT injection_points_wakeup('slot-sync-skip');
+});
+
+done_testing();
-- 
2.34.1

