From ad3ad6bb276bbac0e527d20d1943174667e971b9 Mon Sep 17 00:00:00 2001
From: Oleg Tkachenko <oleg@elsci.io>
Date: Tue, 16 Dec 2025 21:51:21 +0100
Subject: [PATCH] Test the correctness of the truncation_block_length value in
 incremental files.

---
 ...050_incremental_backup_truncation_block.pl | 69 +++++++++++++++++++
 1 file changed, 69 insertions(+)
 create mode 100644 src/bin/pg_basebackup/t/050_incremental_backup_truncation_block.pl

diff --git a/src/bin/pg_basebackup/t/050_incremental_backup_truncation_block.pl b/src/bin/pg_basebackup/t/050_incremental_backup_truncation_block.pl
new file mode 100644
index 00000000000..9b7a7b490e6
--- /dev/null
+++ b/src/bin/pg_basebackup/t/050_incremental_backup_truncation_block.pl
@@ -0,0 +1,69 @@
+# Test case for a scenario where truncation_block is miscalculated during an incremental backup
+
+use strict;
+use warnings FATAL => 'all';
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# Initialize primary node
+my $node = PostgreSQL::Test::Cluster->new('primary');
+$node->init(has_archiving => 1, allows_streaming => 1);
+# Enable WAL summarization to support incremental backup
+$node->append_conf('postgresql.conf', 'summarize_wal = on');
+$node->start;
+
+# Backup locations
+my $backup_path = $node->backup_dir;
+my $full_backup = "$backup_path/full";
+
+# Create a table with wide rows so each tuple occupies almost a full page.
+# STORAGE PLAIN disables compression, allowing simple, repetitive data to be used without worrying about TOAST size.
+$node->safe_psql('postgres', q{
+    CREATE TABLE t (
+        id int,
+        data text STORAGE PLAIN
+    );
+});
+
+# Each tuple occupies almost a full page; having 135k pages guarantees that the relation spans two segments.
+$node->safe_psql('postgres', q{
+    INSERT INTO t
+    SELECT i, repeat('x', 8000)
+    FROM generate_series(1, 135000) i;
+});
+
+# This step is required because at this moment, tuples do not have hint bits set.
+# Later (for example, soon after the base backup is created), a background process may set hint bits
+# on many tuples and change many heap pages.
+# Because of this, the WAL summary may show that too many pages were changed and create a full file copy
+# instead of an incremental one, which makes the issue unproducible.
+$node->safe_psql('postgres', 'VACUUM t;');
+
+# Take a full base backup
+$node->backup('full');
+
+# Delete rows at the logical end of the table. This creates removable empty pages at the tail
+$node->safe_psql('postgres', 'DELETE FROM t WHERE id > 134900;');
+
+# VACUUM with truncation to physically remove trailing empty pages
+$node->safe_psql('postgres', 'VACUUM (TRUNCATE) t;');
+
+# Take an incremental backup based on the full backup manifest
+$node->backup('incr', backup_options => ['--incremental', "$full_backup/backup_manifest"]);
+
+# Combine full and incremental backups.
+# This step must correctly handle truncated relation segments.
+# Before the fix, this failed because the INCREMENTAL file header contained an incorrect truncation_block value.
+my $restored = PostgreSQL::Test::Cluster->new('node2');
+$restored->init_from_backup($node, 'incr', combine_with_prior => ['full']);
+$restored->start();
+
+# Check that the restored table contains the correct number of rows
+my $restored_count = $restored->safe_psql('postgres', "SELECT count(*) FROM t;");
+is($restored_count, '134900', 'Restored backup has correct row count');
+
+$node->stop;
+$restored->stop;
+
+done_testing();
-- 
2.52.0

