From 0fcfaa3691f681a5470399f97ab674873567bf48 Mon Sep 17 00:00:00 2001 From: Robert Haas Date: Tue, 10 Mar 2020 13:44:46 -0400 Subject: [PATCH v10 4/4] Regression tests for pg_validatebackup. Patch by me, based in part on ideas from Mark Dilger and Rajkumar Raghuwanshi. --- src/bin/pg_validatebackup/.gitignore | 1 + src/bin/pg_validatebackup/Makefile | 6 + src/bin/pg_validatebackup/t/001_basic.pl | 27 +++ src/bin/pg_validatebackup/t/002_algorithm.pl | 55 ++++++ src/bin/pg_validatebackup/t/003_corruption.pl | 178 ++++++++++++++++++ 5 files changed, 267 insertions(+) create mode 100644 src/bin/pg_validatebackup/t/001_basic.pl create mode 100644 src/bin/pg_validatebackup/t/002_algorithm.pl create mode 100644 src/bin/pg_validatebackup/t/003_corruption.pl diff --git a/src/bin/pg_validatebackup/.gitignore b/src/bin/pg_validatebackup/.gitignore index 3ae1c1f03a..21e0a92429 100644 --- a/src/bin/pg_validatebackup/.gitignore +++ b/src/bin/pg_validatebackup/.gitignore @@ -1 +1,2 @@ /pg_validatebackup +/tmp_check/ diff --git a/src/bin/pg_validatebackup/Makefile b/src/bin/pg_validatebackup/Makefile index dde7eb3c02..04ef7d3051 100644 --- a/src/bin/pg_validatebackup/Makefile +++ b/src/bin/pg_validatebackup/Makefile @@ -31,3 +31,9 @@ uninstall: clean distclean maintainer-clean: rm -f pg_validatebackup$(X) $(OBJS) + +check: + $(prove_check) + +installcheck: + $(prove_installcheck) diff --git a/src/bin/pg_validatebackup/t/001_basic.pl b/src/bin/pg_validatebackup/t/001_basic.pl new file mode 100644 index 0000000000..49153f89ac --- /dev/null +++ b/src/bin/pg_validatebackup/t/001_basic.pl @@ -0,0 +1,27 @@ +use strict; +use warnings; +use TestLib; +use Test::More tests => 14; + +my $tempdir = TestLib::tempdir; + +program_help_ok('pg_validatebackup'); +program_version_ok('pg_validatebackup'); +program_options_handling_ok('pg_validatebackup'); + +command_fails_like(['pg_validatebackup'], + qr/no backup directory specified/, + 'pg_validatebackup needs target directory specified'); +command_fails_like(['pg_validatebackup', $tempdir], + qr/could not open file.*\/backup_manifest\"/, + 'pg_validatebackup requires a manifest'); + +# create fake manifest file +open(my $fh, '>', "$tempdir/backup_manifest") || die "open: $!"; +close($fh); + +# but then try to use an alternate, nonexisting manifest +command_fails_like(['pg_validatebackup', '-m', "$tempdir/not_the_manifest", + $tempdir], + qr/could not open file.*\/not_the_manifest\"/, + 'pg_validatebackup respects -m flag'); diff --git a/src/bin/pg_validatebackup/t/002_algorithm.pl b/src/bin/pg_validatebackup/t/002_algorithm.pl new file mode 100644 index 0000000000..9bb89d3855 --- /dev/null +++ b/src/bin/pg_validatebackup/t/002_algorithm.pl @@ -0,0 +1,55 @@ +use strict; +use warnings; +use Cwd; +use Config; +use File::Path qw(rmtree); +use PostgresNode; +use TestLib; +use Test::More tests => 19; + +my $master = get_new_node('master'); +$master->init(allows_streaming => 1); +$master->start; + +for my $algorithm (qw(bogus none crc32c sha224 sha256 sha384 sha512)) +{ + my $backup_path = $master->backup_dir . '/' . $algorithm; + my @backup = ('pg_basebackup', '-D', $backup_path, '-m', $algorithm, + '--no-sync'); + my @validate = ('pg_validatebackup', '-e', $backup_path); + + # A backup with a bogus algorithm should fail. + if ($algorithm eq 'bogus') + { + $master->command_fails(\@backup, + "backup fails with algorithm \"$algorithm\""); + next; + } + + # A backup with a valid algorithm should work. + $master->command_ok(\@backup, "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 + # just check that it shows up lots of times. When the checksum algorithm + # is none, we just check that the manifest exists. + if ($algorithm eq 'none') + { + ok(-f "$backup_path/backup_manifest", "backup manifest exists"); + } + else + { + my $manifest = slurp_file("$backup_path/backup_manifest"); + my $count_of_algorithm_in_manifest = + (() = $manifest =~ /$algorithm/mig); + cmp_ok($count_of_algorithm_in_manifest, '>', 100, + "$algorithm is mentioned many times in the manifest"); + } + + # Make sure that it validates OK. + $master->command_ok(\@validate, + "validate backup with algorithm \"$algorithm\""); + + # Remove backup immediately to save disk space. + rmtree($backup_path); +} diff --git a/src/bin/pg_validatebackup/t/003_corruption.pl b/src/bin/pg_validatebackup/t/003_corruption.pl new file mode 100644 index 0000000000..07ed7b6657 --- /dev/null +++ b/src/bin/pg_validatebackup/t/003_corruption.pl @@ -0,0 +1,178 @@ +use strict; +use warnings; +use Cwd; +use Config; +use File::Path qw(rmtree); +use PostgresNode; +use TestLib; +use Test::More tests => 32; + +my $master = get_new_node('master'); +$master->init(allows_streaming => 1); +$master->start; + +# Include a user-defined tablespace in the hopes of detecting problems in that +# area. +my $source_ts_path = TestLib::tempdir; +$master->safe_psql('postgres', < 'extra_file', + 'mutilate' => \&mutilate_extra_file, + 'fails_like' => + qr/extra_file.*present on disk but not in the manifest/ + }, + { + 'name' => 'extra_tablespace_file', + 'mutilate' => \&mutilate_extra_tablespace_file, + 'fails_like' => + qr/extra_ts_file.*present on disk but not in the manifest/ + }, + { + 'name' => 'missing_file', + 'mutilate' => \&mutilate_missing_file, + 'fails_like' => + qr/pg_xact\/0000.*present in the manifest but not on disk/ + }, + { + 'name' => 'missing_tablespace', + 'mutilate' => \&mutilate_missing_tablespace, + 'fails_like' => + qr/pg_tblspc.*present in the manifest but not on disk/ + }, + { + 'name' => 'append_to_file', + 'mutilate' => \&mutilate_append_to_file, + 'fails_like' => + qr/has size \d+ on disk but size \d+ in the manifest/ + }, + { + 'name' => 'truncate_file', + 'mutilate' => \&mutilate_truncate_file, + 'fails_like' => + qr/has size 0 on disk but size \d+ in the manifest/ + }, + { + 'name' => 'replace_file', + 'mutilate' => \&mutilate_replace_file, + 'fails_like' => qr/checksum mismatch for file/ + }, + { + 'name' => 'bad_manifest', + 'mutilate' => \&mutilate_bad_manifest, + 'fails_like' => qr/manifest checksum mismatch/ + } +); + +for my $scenario (@scenario) +{ + my $name = $scenario->{'name'}; + + # Take a backup and check that it validates OK. + my $backup_path = $master->backup_dir . '/' . $name; + my $backup_ts_path = TestLib::tempdir; + $master->command_ok(['pg_basebackup', '-D', $backup_path, '--no-sync', + '-T', "${source_ts_path}=${backup_ts_path}"], + "base backup ok"); + command_ok(['pg_validatebackup', $backup_path ], + "intact backup validated"); + + # Mutilate the backup in some way. + $scenario->{'mutilate'}->($backup_path); + + # Now check that the backup no longer validates. + command_fails_like(['pg_validatebackup', $backup_path ], + $scenario->{'fails_like'}, + "corrupt backup fails validation: $name"); +} + +sub create_extra_file +{ + my ($backup_path, $relative_path) = @_; + my $pathname = "$backup_path/$relative_path"; + open(my $fh, '>', $pathname) || die "open $pathname: $!"; + print $fh "This is an extra file.\n"; + close($fh); +} + +# Add a file into the root directory of the backup. +sub mutilate_extra_file +{ + my ($backup_path) = @_; + create_extra_file($backup_path, "extra_file"); +} + +# Add a file inside the user-defined tablespace. +sub mutilate_extra_tablespace_file +{ + my ($backup_path) = @_; + my ($tsoid) = grep { $_ ne '.' && $_ ne '..' } + slurp_dir("$backup_path/pg_tblspc"); + my ($catvdir) = grep { $_ ne '.' && $_ ne '..' } + slurp_dir("$backup_path/pg_tblspc/$tsoid"); + my ($tsdboid) = grep { $_ ne '.' && $_ ne '..' } + slurp_dir("$backup_path/pg_tblspc/$tsoid/$catvdir"); + create_extra_file($backup_path, + "pg_tblspc/$tsoid/$catvdir/$tsdboid/extra_ts_file"); +} + +# Remove a file. +sub mutilate_missing_file +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/pg_xact/0000"; + unlink($pathname) || die "$pathname: $!"; +} + +# Remove the symlink to the user-defined tablespace. +sub mutilate_missing_tablespace +{ + my ($backup_path) = @_; + my ($tsoid) = grep { $_ ne '.' && $_ ne '..' } + slurp_dir("$backup_path/pg_tblspc"); + my $pathname = "$backup_path/pg_tblspc/$tsoid"; + unlink($pathname) || die "$pathname: $!"; +} + +# Append an additional bytes to a file. +sub mutilate_append_to_file +{ + my ($backup_path) = @_; + append_to_file "$backup_path/global/pg_control", 'x'; +} + +# Truncate a file to zero length. +sub mutilate_truncate_file +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/global/pg_control"; + open(my $fh, '>', $pathname) || die "open $pathname: $!"; + close($fh); +} + +# Replace a file's contents without changing the length of the file. This is +# not a particularly efficient way to do this, so we pick a file that's +# expected to be short. +sub mutilate_replace_file +{ + my ($backup_path) = @_; + my $pathname = "$backup_path/PG_VERSION"; + my $contents = slurp_file($pathname); + open(my $fh, '>', $pathname) || die "open $pathname: $!"; + print $fh 'q' x length($contents); + close($fh); +} + +# Corrupt the backup manifest. +sub mutilate_bad_manifest +{ + my ($backup_path) = @_; + append_to_file "$backup_path/backup_manifest", "\n"; +} -- 2.17.2 (Apple Git-113)