# Copyright (c) 2021-2025, PostgreSQL Global Development Group use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Utils; use Test::More; program_help_ok('pg_archivecleanup'); program_version_ok('pg_archivecleanup'); program_options_handling_ok('pg_archivecleanup'); my $tempdir = PostgreSQL::Test::Utils::tempdir; # WAL file patterns created before running each sub-scenario. "present" # tracks if the file with "name" still exists or not after running # pg_archivecleanup. my @walfiles_verbose = ( { name => '00000001000000370000000D', present => 0 }, { name => '00000001000000370000000E', present => 1 }); my @walfiles_with_gz = ( { name => '00000001000000370000000C.gz', present => 0 }, { name => '00000001000000370000000D', present => 0 }, { name => '00000001000000370000000D.backup', present => 1 }, { name => '00000001000000370000000E', present => 1 }, { name => '00000001000000370000000F.partial', present => 1 }, { name => 'unrelated_file', present => 1 }); my @walfiles_for_clean_backup_history = ( { name => '00000001000000370000000D', present => 0 }, { name => '00000001000000370000000D.00000028.backup', present => 0 }, { name => '00000001000000370000000E', present => 1 }, { name => '00000001000000370000000F.partial', present => 1 }, { name => 'unrelated_file', present => 1 }); sub create_files { foreach my $fn (map { $_->{name} } @_) { open my $file, '>', "$tempdir/$fn" or die $!; print $file 'CONTENT'; close $file; } return; } sub remove_files { foreach my $fn (map { $_->{name} } @_) { unlink "$tempdir/$fn"; } return; } command_fails_like( ['pg_archivecleanup'], qr/must specify archive location/, 'fails if archive location is not specified'); command_fails_like( [ 'pg_archivecleanup', $tempdir ], qr/must specify oldest kept WAL file/, 'fails if oldest kept WAL file name is not specified'); command_fails_like( [ 'pg_archivecleanup', 'notexist', 'foo' ], qr/archive location .* does not exist/, 'fails if archive location does not exist'); command_fails_like( [ 'pg_archivecleanup', $tempdir, 'foo', 'bar' ], qr/too many command-line arguments/, 'fails with too many command-line arguments'); command_fails_like( [ 'pg_archivecleanup', $tempdir, 'foo' ], qr/invalid file name argument/, 'fails with invalid restart file name'); # Test a dry run, no files are physically removed, but logs are generated # to show what would be removed. { create_files(@walfiles_verbose); my $stderr; my $oldestkeptwalfile = '00000001000000370000000E'; my $result = IPC::Run::run [ 'pg_archivecleanup', '--debug', '--dry-run', $tempdir, $oldestkeptwalfile ], '2>' => \$stderr; ok($result, "pg_archivecleanup dry run: exit code 0"); for my $walpair (@walfiles_verbose) { if ($walpair->{present}) { unlike( $stderr, qr/$walpair->{name}.*would be removed/, "pg_archivecleanup dry run for $walpair->{name}: matches"); } else { like( $stderr, qr/$walpair->{name}.*would be removed/, "pg_archivecleanup dry run for $walpair->{name}: matches"); } } foreach my $fn (map { $_->{name} } @walfiles_verbose) { ok(-f "$tempdir/$fn", "$fn not removed"); } remove_files(@walfiles_verbose); } sub run_check { local $Test::Builder::Level = $Test::Builder::Level + 1; my ($testdata, $oldestkeptwalfile, $test_name, @options) = @_; create_files(@$testdata); command_ok( [ 'pg_archivecleanup', @options, $tempdir, $oldestkeptwalfile ], "$test_name: runs"); for my $walpair (@$testdata) { if ($walpair->{present}) { ok(-f "$tempdir/$walpair->{name}", "$test_name:$walpair->{name} was not cleaned up"); } else { ok(!-f "$tempdir/$walpair->{name}", "$test_name:$walpair->{name} was cleaned up"); } } remove_files(@$testdata); return; } run_check(\@walfiles_with_gz, '00000001000000370000000E', 'pg_archivecleanup', '-x.gz'); run_check( \@walfiles_with_gz, '00000001000000370000000E.partial', 'pg_archivecleanup with .partial file', '-x.gz'); run_check( \@walfiles_with_gz, '00000001000000370000000E.00000020.backup', 'pg_archivecleanup with .backup file', '-x.gz'); run_check(\@walfiles_for_clean_backup_history, '00000001000000370000000E', 'pg_archivecleanup with --clean-backup-history', '-b'); done_testing();