v14-0012-pg_verifybackup-Tests-and-document.patch

application/x-patch

Filename: v14-0012-pg_verifybackup-Tests-and-document.patch
Type: application/x-patch
Part: 6
Message: Re: pg_verifybackup: TAR format backup verification

Patch

Format: format-patch
Series: patch v14-0012
Subject: pg_verifybackup: Tests and document
File+
doc/src/sgml/ref/pg_verifybackup.sgml 43 2
src/bin/pg_verifybackup/t/002_algorithm.pl 27 7
src/bin/pg_verifybackup/t/003_corruption.pl 256 11
src/bin/pg_verifybackup/t/004_options.pl 1 1
src/bin/pg_verifybackup/t/008_untar.pl 22 49
src/bin/pg_verifybackup/t/010_client_untar.pl 3 45
From aeed7ab45f6c15abae8e4935b61c3659a9ba139e Mon Sep 17 00:00:00 2001
From: Amul Sul <amul.sul@enterprisedb.com>
Date: Thu, 12 Sep 2024 15:35:49 +0530
Subject: [PATCH v14 12/12] pg_verifybackup: Tests and document

----
NOTE: This patch is not meant to be committed separately. It should
be squashed with the previous patch that implements tar format support.
----
---
 doc/src/sgml/ref/pg_verifybackup.sgml         |  45 ++-
 src/bin/pg_verifybackup/t/002_algorithm.pl    |  34 ++-
 src/bin/pg_verifybackup/t/003_corruption.pl   | 267 +++++++++++++++++-
 src/bin/pg_verifybackup/t/004_options.pl      |   2 +-
 src/bin/pg_verifybackup/t/008_untar.pl        |  71 ++---
 src/bin/pg_verifybackup/t/010_client_untar.pl |  48 +---
 6 files changed, 352 insertions(+), 115 deletions(-)

diff --git a/doc/src/sgml/ref/pg_verifybackup.sgml b/doc/src/sgml/ref/pg_verifybackup.sgml
index a3f167f9f6e..8380178ca49 100644
--- a/doc/src/sgml/ref/pg_verifybackup.sgml
+++ b/doc/src/sgml/ref/pg_verifybackup.sgml
@@ -34,8 +34,12 @@ PostgreSQL documentation
    integrity of a database cluster backup taken using
    <command>pg_basebackup</command> against a
    <literal>backup_manifest</literal> generated by the server at the time
-   of the backup.  The backup must be stored in the "plain"
-   format; a "tar" format backup can be checked after extracting it.
+   of the backup. The backup may be stored either in the "plain" or the "tar"
+   format; this includes tar-format backups compressed with any algorithm
+   supported by <application>pg_basebackup</application>. However, at present,
+   <literal>WAL</literal> verification is supported only for plain-format
+   backups. Therefore, if the backup is stored in tar-format, the
+   <literal>-n, --no-parse-wal</literal> option should be used.
   </para>
 
   <para>
@@ -168,6 +172,43 @@ PostgreSQL documentation
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>-F <replaceable class="parameter">format</replaceable></option></term>
+      <term><option>--format=<replaceable class="parameter">format</replaceable></option></term>
+      <listitem>
+       <para>
+        Specifies the format of the backup. <replaceable>format</replaceable>
+        can be one of the following:
+
+        <variablelist>
+         <varlistentry>
+          <term><literal>p</literal></term>
+          <term><literal>plain</literal></term>
+          <listitem>
+           <para>
+            Backup consists of plain files with the same layout as the
+            source server's data directory and tablespaces.
+           </para>
+          </listitem>
+         </varlistentry>
+
+         <varlistentry>
+          <term><literal>t</literal></term>
+          <term><literal>tar</literal></term>
+          <listitem>
+           <para>
+            Backup consists of tar files.  A valid backup includes the main data
+            directory in a file named <filename>base.tar</filename>, the WAL
+            files in <filename>pg_wal.tar</filename>, and separate tar files for
+            each tablespace, named after the tablespace's OID, followed by the
+            compression extension.
+           </para>
+           </listitem>
+         </varlistentry>
+        </variablelist></para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-n</option></term>
       <term><option>--no-parse-wal</option></term>
diff --git a/src/bin/pg_verifybackup/t/002_algorithm.pl b/src/bin/pg_verifybackup/t/002_algorithm.pl
index fb2a1fd7c4e..4959d5bd0b9 100644
--- a/src/bin/pg_verifybackup/t/002_algorithm.pl
+++ b/src/bin/pg_verifybackup/t/002_algorithm.pl
@@ -14,24 +14,35 @@ my $primary = PostgreSQL::Test::Cluster->new('primary');
 $primary->init(allows_streaming => 1);
 $primary->start;
 
-for my $algorithm (qw(bogus none crc32c sha224 sha256 sha384 sha512))
+sub test_checksums
 {
-	my $backup_path = $primary->backup_dir . '/' . $algorithm;
+	my ($format, $algorithm) = @_;
+	my $backup_path = $primary->backup_dir . '/' . $format . '/' . $algorithm;
 	my @backup = (
 		'pg_basebackup', '-D', $backup_path,
 		'--manifest-checksums', $algorithm, '--no-sync', '-cfast');
 	my @verify = ('pg_verifybackup', '-e', $backup_path);
 
+	if ($format eq 'tar')
+	{
+		# Add switch to get a tar-format backup
+		push @backup, ('-F', 't');
+
+		# Add switch to skip WAL verification, which is not yet supported for
+		# tar-format backups
+		push @verify, ('-n');
+	}
+
 	# A backup with a bogus algorithm should fail.
 	if ($algorithm eq 'bogus')
 	{
 		$primary->command_fails(\@backup,
-			"backup fails with algorithm \"$algorithm\"");
-		next;
+			"$format format backup fails with algorithm \"$algorithm\"");
+		return;
 	}
 
 	# A backup with a valid algorithm should work.
-	$primary->command_ok(\@backup, "backup ok with algorithm \"$algorithm\"");
+	$primary->command_ok(\@backup, "$format format backup ok with algorithm \"$algorithm\"");
 
 	# We expect each real checksum algorithm to be mentioned on every line of
 	# the backup manifest file except the first and last; for simplicity, we
@@ -39,7 +50,7 @@ for my $algorithm (qw(bogus none crc32c sha224 sha256 sha384 sha512))
 	# is none, we just check that the manifest exists.
 	if ($algorithm eq 'none')
 	{
-		ok(-f "$backup_path/backup_manifest", "backup manifest exists");
+		ok(-f "$backup_path/backup_manifest", "$format format backup manifest exists");
 	}
 	else
 	{
@@ -52,10 +63,19 @@ for my $algorithm (qw(bogus none crc32c sha224 sha256 sha384 sha512))
 
 	# Make sure that it verifies OK.
 	$primary->command_ok(\@verify,
-		"verify backup with algorithm \"$algorithm\"");
+		"verify $format format backup with algorithm \"$algorithm\"");
 
 	# Remove backup immediately to save disk space.
 	rmtree($backup_path);
 }
 
+# Do the check
+for my $format (qw(plain tar))
+{
+	for my $algorithm (qw(bogus none crc32c sha224 sha256 sha384 sha512))
+	{
+		test_checksums($format, $algorithm);
+	}
+}
+
 done_testing();
diff --git a/src/bin/pg_verifybackup/t/003_corruption.pl b/src/bin/pg_verifybackup/t/003_corruption.pl
index ae91e043384..c953bbc20d8 100644
--- a/src/bin/pg_verifybackup/t/003_corruption.pl
+++ b/src/bin/pg_verifybackup/t/003_corruption.pl
@@ -11,6 +11,8 @@ use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
 use Test::More;
 
+my $tar = $ENV{TAR};
+
 my $primary = PostgreSQL::Test::Cluster->new('primary');
 $primary->init(allows_streaming => 1);
 $primary->start;
@@ -32,62 +34,73 @@ EOM
 my @scenario = (
 	{
 		'name' => 'extra_file',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_extra_file,
 		'fails_like' =>
 		  qr/extra_file.*present on disk but not in the manifest/
 	},
 	{
 		'name' => 'extra_tablespace_file',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_extra_tablespace_file,
 		'fails_like' =>
 		  qr/extra_ts_file.*present on disk but not in the manifest/
 	},
 	{
 		'name' => 'missing_file',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_missing_file,
 		'fails_like' =>
 		  qr/pg_xact\/0000.*present in the manifest but not on disk/
 	},
 	{
 		'name' => 'missing_tablespace',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_missing_tablespace,
 		'fails_like' =>
 		  qr/pg_tblspc.*present in the manifest but not on disk/
 	},
 	{
 		'name' => 'append_to_file',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_append_to_file,
 		'fails_like' => qr/has size \d+ on disk but size \d+ in the manifest/
 	},
 	{
 		'name' => 'truncate_file',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_truncate_file,
 		'fails_like' => qr/has size 0 on disk but size \d+ in the manifest/
 	},
 	{
 		'name' => 'replace_file',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_replace_file,
 		'fails_like' => qr/checksum mismatch for file/
 	},
 	{
 		'name' => 'system_identifier',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_system_identifier,
 		'fails_like' =>
 		  qr/manifest system identifier is .*, but control file has/
 	},
 	{
 		'name' => 'bad_manifest',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_bad_manifest,
 		'fails_like' => qr/manifest checksum mismatch/
 	},
 	{
 		'name' => 'open_file_fails',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_open_file_fails,
 		'fails_like' => qr/could not open file/,
 		'skip_on_windows' => 1
 	},
 	{
 		'name' => 'open_directory_fails',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_open_directory_fails,
 		'cleanup' => \&cleanup_open_directory_fails,
 		'fails_like' => qr/could not open directory/,
@@ -95,10 +108,78 @@ my @scenario = (
 	},
 	{
 		'name' => 'search_directory_fails',
+		'backup_format' => 'p',
 		'mutilate' => \&mutilate_search_directory_fails,
 		'cleanup' => \&cleanup_search_directory_fails,
 		'fails_like' => qr/could not stat file or directory/,
 		'skip_on_windows' => 1
+	},
+	{
+		'name' => 'tar_backup_unexpected_file',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_extra_file,
+		'fails_like' =>
+		  qr/file "extra_file" is not expected in a tar format backup/
+	},
+	{
+		'name' => 'tar_backup_extra_file',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_tar_backup_extra_file,
+		'fails_like' =>
+		  qr/extra_tar_member_file.*present on disk but not in the manifest/
+	},
+	{
+		'name' => 'tar_extra_tablespace_file',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_tar_extra_tablespace_file,
+		'fails_like' =>
+		  qr/extra_ts_member_file.*present on disk but not in the manifest/
+	},
+	{
+		'name' => 'tar_backup_missing_file',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_tar_backup_missing_file,
+		'fails_like' =>
+		  qr/pg_xact\/0000.*present in the manifest but not on disk/,
+	},
+	{
+		'name' => 'tar_missing_tablespace',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_tar_missing_tablespace,
+		'fails_like' =>
+		  qr/pg_tblspc.*present in the manifest but not on disk/
+	},
+	{
+		'name' => 'tar_missing_tablespace_file',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_tar_missing_tablespace_file,
+		'fails_like' =>
+		  qr/pg_tblspc.*present in the manifest but not on disk/
+	},
+	{
+		'name' => 'tar_backup_append_to_file',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_tar_backup_append_to_file,
+		'fails_like' => qr/has size \d+ on disk but size \d+ in the manifest/,
+	},
+	{
+		'name' => 'tar_backup_truncate_file',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_tar_backup_truncate_file,
+		'fails_like' => qr/has size 0 on disk but size \d+ in the manifest/,
+	},
+	{
+		'name' => 'tar_backup_replace_file',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_tar_backup_replace_file,
+		'fails_like' => qr/checksum mismatch for file/,
+	},
+	{
+		'name' => 'tar_backup_system_identifier',
+		'backup_format' => 't',
+		'mutilate' => \&mutilate_system_identifier,
+		'fails_like' =>
+		  qr/manifest system identifier is .*, but control file has/
 	});
 
 for my $scenario (@scenario)
@@ -111,29 +192,42 @@ for my $scenario (@scenario)
 		  if ($scenario->{'skip_on_windows'}
 			&& ($windows_os || $Config::Config{osname} eq 'cygwin'));
 
+		# Skip tests for tar format-backup if tar is not available.
+		skip "no tar program available", 4
+		if ($scenario->{'backup_format'} eq 't' && (!defined $tar || $tar eq ''));
+
 		# Take a backup and check that it verifies OK.
 		my $backup_path = $primary->backup_dir . '/' . $name;
 		my $backup_ts_path = PostgreSQL::Test::Utils::tempdir_short();
+
+		my @backup = (
+				'pg_basebackup', '-D', $backup_path, '--no-sync', '-cfast',
+				'-T', "${source_ts_path}=${backup_ts_path}");
+		my @verify = ('pg_verifybackup', $backup_path);
+
+		if ($scenario->{'backup_format'} eq 't')
+		{
+			# Add switch to get a tar-format backup
+			push @backup, ('-F', 't');
+
+			# Add switch to skip WAL verification, which is not yet supported
+			# for tar-format backups
+			push @verify, ('-n');
+		}
+
 		# The tablespace map parameter confuses Msys2, which tries to mangle
 		# it. Tell it not to.
 		# See https://www.msys2.org/wiki/Porting/#filesystem-namespaces
 		local $ENV{MSYS2_ARG_CONV_EXCL} = $source_ts_prefix;
-		$primary->command_ok(
-			[
-				'pg_basebackup', '-D', $backup_path, '--no-sync', '-cfast',
-				'-T', "${source_ts_path}=${backup_ts_path}"
-			],
-			"base backup ok");
-		command_ok([ 'pg_verifybackup', $backup_path ],
-			"intact backup verified");
+
+		$primary->command_ok( \@backup, "base backup ok");
+		command_ok(\@verify, "intact backup verified");
 
 		# Mutilate the backup in some way.
 		$scenario->{'mutilate'}->($backup_path);
 
 		# Now check that the backup no longer verifies.
-		command_fails_like(
-			[ 'pg_verifybackup', $backup_path ],
-			$scenario->{'fails_like'},
+		command_fails_like(\@verify, $scenario->{'fails_like'},
 			"corrupt backup fails verification: $name");
 
 		# Run cleanup hook, if provided.
@@ -260,6 +354,7 @@ sub mutilate_system_identifier
 		$backup_path . '/backup_manifest')
 	  or BAIL_OUT "could not copy manifest to $backup_path";
 	$node->teardown_node(fail_ok => 1);
+	$node->clean_node();
 	return;
 }
 
@@ -316,4 +411,154 @@ sub cleanup_search_directory_fails
 	return;
 }
 
+# Unpack tar file, perform the specified file operation, and then repack the
+# modified content into same tar file at the same location.
+sub mutilate_base_tar
+{
+	my ($backup_path, $archive, $op) = @_;
+
+	my $tmpdir = "$backup_path/tmpdir";
+	mkdir($tmpdir) || die "$!";
+
+	# Extract the archive
+	system_or_bail($tar, '-xf', "$backup_path/$archive", '-C', "$tmpdir");
+	unlink("$backup_path/$archive") || die "$!";
+
+	if ($op eq 'add')
+	{
+		if ($archive eq 'base.tar')
+		{
+			create_extra_file($tmpdir, 'extra_tar_member_file');
+		}
+		else
+		{
+			# must be a tablespace archive
+			my ($catvdir) = grep { $_ ne '.' && $_ ne '..' }
+			slurp_dir("$tmpdir");
+			my ($tsdboid) = grep { $_ ne '.' && $_ ne '..' }
+			slurp_dir("$tmpdir/$catvdir");
+			create_extra_file($tmpdir,
+				"$catvdir/$tsdboid/extra_ts_member_file");
+		}
+	}
+	elsif ($op eq 'delete')
+	{
+		if ($archive eq 'base.tar')
+		{
+			mutilate_missing_file($tmpdir);
+		}
+		else
+		{
+			# must be a tablespace archive
+			my ($catvdir) = grep { $_ ne '.' && $_ ne '..' }
+			  slurp_dir("$tmpdir");
+			my ($tsdboid) = grep { $_ ne '.' && $_ ne '..' }
+			  slurp_dir("$tmpdir/$catvdir");
+			my ($reloid) = grep { $_ ne '.' && $_ ne '..' }
+			  slurp_dir("$tmpdir/$catvdir/$tsdboid");
+			my $pathname = "$tmpdir/$catvdir/$tsdboid/$reloid";
+			unlink($pathname) || die "$pathname: $!";
+		}
+	}
+	elsif ($op eq 'append')
+	{
+		mutilate_append_to_file($tmpdir);
+	}
+	elsif ($op eq 'truncate')
+	{
+		mutilate_truncate_file($tmpdir);
+	}
+	elsif ($op eq 'replace')
+	{
+		mutilate_replace_file($tmpdir);
+	}
+	else
+	{
+		die "mutilate_tar_backup: \"$op\" invalid operation";
+	}
+
+
+	# Navigate to the extracted location and list the files.
+	chdir("$tmpdir") || die "$!";
+	my @files = glob("*");
+	# Repack the extracted content
+	system_or_bail($tar, '-cf', "$backup_path/$archive", @files);
+	chdir($backup_path) || die "$!";
+	rmtree("$tmpdir") || die "$!";
+}
+
+# Add a file to the main directory archive.
+sub mutilate_tar_backup_extra_file
+{
+	my ($backup_path) = @_;
+	mutilate_base_tar($backup_path, 'base.tar', 'add');
+	return;
+}
+
+# Add a file to the user-defined tablespace archive.
+sub mutilate_tar_extra_tablespace_file
+{
+	my ($backup_path) = @_;
+	my ($archive) =
+	  grep { $_ =~ qr/\d+\.tar/ } slurp_dir("$backup_path");
+	die "tablespace tar backup not found." unless defined $archive;
+	mutilate_base_tar($backup_path, $archive, 'add');
+	return;
+}
+
+# Remove a file from main directory archive.
+sub mutilate_tar_backup_missing_file
+{
+	my ($backup_path) = @_;
+	mutilate_base_tar($backup_path, 'base.tar', 'delete');
+	return;
+}
+
+# Remove the user-defined tablespace archive.
+sub mutilate_tar_missing_tablespace
+{
+	my ($backup_path) = @_;
+	my ($archive) =
+	  grep { $_ =~ qr/\d+\.tar/ } slurp_dir("$backup_path");
+	die "tablespace tar backup not found." unless defined $archive;
+	my $pathname = "$backup_path/$archive";
+	unlink($pathname) || die "$pathname: $!";
+	return;
+}
+
+# Remove the files from the user-defined tablespace archive.
+sub mutilate_tar_missing_tablespace_file
+{
+	my ($backup_path) = @_;
+	my ($archive) =
+	  grep { $_ =~ qr/\d+\.tar/ } slurp_dir("$backup_path");
+	die "tablespace tar backup not found." unless defined $archive;
+	mutilate_base_tar($backup_path, $archive, 'delete');
+	return;
+}
+
+# Append an additional bytes to a file.
+sub mutilate_tar_backup_append_to_file
+{
+	my ($backup_path) = @_;
+	mutilate_base_tar($backup_path, 'base.tar', 'append');
+	return;
+}
+
+# Truncate a file to zero length.
+sub mutilate_tar_backup_truncate_file
+{
+	my ($backup_path) = @_;
+	mutilate_base_tar($backup_path, 'base.tar', 'truncate');
+	return;
+}
+
+# Replace a file's contents
+sub mutilate_tar_backup_replace_file
+{
+	my ($backup_path) = @_;
+	mutilate_base_tar($backup_path, 'base.tar', 'replace');
+	return;
+}
+
 done_testing();
diff --git a/src/bin/pg_verifybackup/t/004_options.pl b/src/bin/pg_verifybackup/t/004_options.pl
index 8ed2214408e..2f197648740 100644
--- a/src/bin/pg_verifybackup/t/004_options.pl
+++ b/src/bin/pg_verifybackup/t/004_options.pl
@@ -108,7 +108,7 @@ unlike(
 # Test valid manifest with nonexistent backup directory.
 command_fails_like(
 	[
-		'pg_verifybackup', '-m',
+		'pg_verifybackup', '-Fp', '-m',
 		"$backup_path/backup_manifest", "$backup_path/fake"
 	],
 	qr/could not open directory/,
diff --git a/src/bin/pg_verifybackup/t/008_untar.pl b/src/bin/pg_verifybackup/t/008_untar.pl
index 7a09f3b75b2..e7ec8369362 100644
--- a/src/bin/pg_verifybackup/t/008_untar.pl
+++ b/src/bin/pg_verifybackup/t/008_untar.pl
@@ -16,6 +16,18 @@ my $primary = PostgreSQL::Test::Cluster->new('primary');
 $primary->init(allows_streaming => 1);
 $primary->start;
 
+# Create a tablespace directory.
+my $source_ts_path = PostgreSQL::Test::Utils::tempdir_short();
+
+# Create a tablespace with table in it.
+$primary->safe_psql('postgres', qq(
+		CREATE TABLESPACE regress_ts1 LOCATION '$source_ts_path';
+		SELECT oid FROM pg_tablespace WHERE spcname = 'regress_ts1';
+		CREATE TABLE regress_tbl1(i int) TABLESPACE regress_ts1;
+		INSERT INTO regress_tbl1 VALUES(generate_series(1,5));));
+my $tsoid = $primary->safe_psql('postgres', qq(
+		SELECT oid FROM pg_tablespace WHERE spcname = 'regress_ts1'));
+
 my $backup_path = $primary->backup_dir . '/server-backup';
 my $extract_path = $primary->backup_dir . '/extracted-backup';
 
@@ -23,39 +35,31 @@ my @test_configuration = (
 	{
 		'compression_method' => 'none',
 		'backup_flags' => [],
-		'backup_archive' => 'base.tar',
+		'backup_archive' => ['base.tar', "$tsoid.tar"],
 		'enabled' => 1
 	},
 	{
 		'compression_method' => 'gzip',
 		'backup_flags' => [ '--compress', 'server-gzip' ],
-		'backup_archive' => 'base.tar.gz',
-		'decompress_program' => $ENV{'GZIP_PROGRAM'},
-		'decompress_flags' => ['-d'],
+		'backup_archive' => [ 'base.tar.gz', "$tsoid.tar.gz" ],
 		'enabled' => check_pg_config("#define HAVE_LIBZ 1")
 	},
 	{
 		'compression_method' => 'lz4',
 		'backup_flags' => [ '--compress', 'server-lz4' ],
-		'backup_archive' => 'base.tar.lz4',
-		'decompress_program' => $ENV{'LZ4'},
-		'decompress_flags' => [ '-d', '-m' ],
+		'backup_archive' => ['base.tar.lz4', "$tsoid.tar.lz4" ],
 		'enabled' => check_pg_config("#define USE_LZ4 1")
 	},
 	{
 		'compression_method' => 'zstd',
 		'backup_flags' => [ '--compress', 'server-zstd' ],
-		'backup_archive' => 'base.tar.zst',
-		'decompress_program' => $ENV{'ZSTD'},
-		'decompress_flags' => ['-d'],
+		'backup_archive' => [ 'base.tar.zst', "$tsoid.tar.zst" ],
 		'enabled' => check_pg_config("#define USE_ZSTD 1")
 	},
 	{
 		'compression_method' => 'zstd',
 		'backup_flags' => [ '--compress', 'server-zstd:level=1,long' ],
-		'backup_archive' => 'base.tar.zst',
-		'decompress_program' => $ENV{'ZSTD'},
-		'decompress_flags' => ['-d'],
+		'backup_archive' => [ 'base.tar.zst', "$tsoid.tar.zst" ],
 		'enabled' => check_pg_config("#define USE_ZSTD 1")
 	});
 
@@ -86,47 +90,16 @@ for my $tc (@test_configuration)
 		my $backup_files = join(',',
 			sort grep { $_ ne '.' && $_ ne '..' } slurp_dir($backup_path));
 		my $expected_backup_files =
-		  join(',', sort ('backup_manifest', $tc->{'backup_archive'}));
+		  join(',', sort ('backup_manifest', @{ $tc->{'backup_archive'} }));
 		is($backup_files, $expected_backup_files,
 			"found expected backup files, compression $method");
 
-		# Decompress.
-		if (exists $tc->{'decompress_program'})
-		{
-			my @decompress = ($tc->{'decompress_program'});
-			push @decompress, @{ $tc->{'decompress_flags'} }
-			  if $tc->{'decompress_flags'};
-			push @decompress, $backup_path . '/' . $tc->{'backup_archive'};
-			system_or_bail(@decompress);
-		}
-
-	  SKIP:
-		{
-			my $tar = $ENV{TAR};
-			# don't check for a working tar here, to accommodate various odd
-			# cases. If tar doesn't work the init_from_backup below will fail.
-			skip "no tar program available", 1
-			  if (!defined $tar || $tar eq '');
-
-			# Untar.
-			mkdir($extract_path);
-			system_or_bail($tar, 'xf', $backup_path . '/base.tar',
-				'-C', $extract_path);
-
-			# Verify.
-			$primary->command_ok(
-				[
-					'pg_verifybackup', '-n',
-					'-m', "$backup_path/backup_manifest",
-					'-e', $extract_path
-				],
-				"verify backup, compression $method");
-		}
+		# Verify tar backup.
+		$primary->command_ok(['pg_verifybackup', '-n', '-e', $backup_path],
+			"verify backup, compression $method");
 
 		# Cleanup.
-		unlink($backup_path . '/backup_manifest');
-		unlink($backup_path . '/base.tar');
-		unlink($backup_path . '/' . $tc->{'backup_archive'});
+		rmtree($backup_path);
 		rmtree($extract_path);
 	}
 }
diff --git a/src/bin/pg_verifybackup/t/010_client_untar.pl b/src/bin/pg_verifybackup/t/010_client_untar.pl
index 8c076d46dee..6b7d7483f6e 100644
--- a/src/bin/pg_verifybackup/t/010_client_untar.pl
+++ b/src/bin/pg_verifybackup/t/010_client_untar.pl
@@ -29,41 +29,30 @@ my @test_configuration = (
 		'compression_method' => 'gzip',
 		'backup_flags' => [ '--compress', 'client-gzip:5' ],
 		'backup_archive' => 'base.tar.gz',
-		'decompress_program' => $ENV{'GZIP_PROGRAM'},
-		'decompress_flags' => ['-d'],
 		'enabled' => check_pg_config("#define HAVE_LIBZ 1")
 	},
 	{
 		'compression_method' => 'lz4',
 		'backup_flags' => [ '--compress', 'client-lz4:5' ],
 		'backup_archive' => 'base.tar.lz4',
-		'decompress_program' => $ENV{'LZ4'},
-		'decompress_flags' => ['-d'],
-		'output_file' => 'base.tar',
 		'enabled' => check_pg_config("#define USE_LZ4 1")
 	},
 	{
 		'compression_method' => 'zstd',
 		'backup_flags' => [ '--compress', 'client-zstd:5' ],
 		'backup_archive' => 'base.tar.zst',
-		'decompress_program' => $ENV{'ZSTD'},
-		'decompress_flags' => ['-d'],
 		'enabled' => check_pg_config("#define USE_ZSTD 1")
 	},
 	{
 		'compression_method' => 'zstd',
 		'backup_flags' => [ '--compress', 'client-zstd:level=1,long' ],
 		'backup_archive' => 'base.tar.zst',
-		'decompress_program' => $ENV{'ZSTD'},
-		'decompress_flags' => ['-d'],
 		'enabled' => check_pg_config("#define USE_ZSTD 1")
 	},
 	{
 		'compression_method' => 'parallel zstd',
 		'backup_flags' => [ '--compress', 'client-zstd:workers=3' ],
 		'backup_archive' => 'base.tar.zst',
-		'decompress_program' => $ENV{'ZSTD'},
-		'decompress_flags' => ['-d'],
 		'enabled' => check_pg_config("#define USE_ZSTD 1"),
 		'possibly_unsupported' =>
 		  qr/could not set compression worker count to 3: Unsupported parameter/
@@ -118,40 +107,9 @@ for my $tc (@test_configuration)
 		is($backup_files, $expected_backup_files,
 			"found expected backup files, compression $method");
 
-		# Decompress.
-		if (exists $tc->{'decompress_program'})
-		{
-			my @decompress = ($tc->{'decompress_program'});
-			push @decompress, @{ $tc->{'decompress_flags'} }
-			  if $tc->{'decompress_flags'};
-			push @decompress, $backup_path . '/' . $tc->{'backup_archive'};
-			push @decompress, $backup_path . '/' . $tc->{'output_file'}
-			  if $tc->{'output_file'};
-			system_or_bail(@decompress);
-		}
-
-	  SKIP:
-		{
-			my $tar = $ENV{TAR};
-			# don't check for a working tar here, to accommodate various odd
-			# cases. If tar doesn't work the init_from_backup below will fail.
-			skip "no tar program available", 1
-			  if (!defined $tar || $tar eq '');
-
-			# Untar.
-			mkdir($extract_path);
-			system_or_bail($tar, 'xf', $backup_path . '/base.tar',
-				'-C', $extract_path);
-
-			# Verify.
-			$primary->command_ok(
-				[
-					'pg_verifybackup', '-n',
-					'-m', "$backup_path/backup_manifest",
-					'-e', $extract_path
-				],
-				"verify backup, compression $method");
-		}
+		# Verify tar backup.
+		$primary->command_ok( [ 'pg_verifybackup', '-n', '-e', $backup_path ],
+			"verify backup, compression $method");
 
 		# Cleanup.
 		rmtree($extract_path);
-- 
2.18.0