diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl index 299dcf5b3b..f64186d637 100644 --- a/src/bin/initdb/t/001_initdb.pl +++ b/src/bin/initdb/t/001_initdb.pl @@ -4,6 +4,7 @@ use strict; use warnings; +use PostgresNode; use TestLib; use Test::More tests => 14; diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl index dc96bbf206..3e491a82e4 100644 --- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl +++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl @@ -2,6 +2,7 @@ use strict; use warnings; use Cwd; use Config; +use PostgresNode; use TestLib; use Test::More tests => 51; @@ -9,12 +10,18 @@ program_help_ok('pg_basebackup'); program_version_ok('pg_basebackup'); program_options_handling_ok('pg_basebackup'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $tempdir = TestLib::tempdir; -command_fails(['pg_basebackup'], +my $node = get_new_node(); + +# Initialize node without replication settings +$node->init(hba_permit_replication => 0); +$node->start; +my $pgdata = $node->data_dir; + +$node->command_fails(['pg_basebackup'], 'pg_basebackup needs target directory specified'); -command_fails( +$node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup" ], 'pg_basebackup fails because of hba'); @@ -26,160 +33,191 @@ if (open BADCHARS, ">>$tempdir/pgdata/FOO\xe0\xe0\xe0BAR") close BADCHARS; } -configure_hba_for_replication "$tempdir/pgdata"; -system_or_bail 'pg_ctl', '-D', "$tempdir/pgdata", 'reload'; +$node->set_replication_conf(); +system_or_bail 'pg_ctl', '-D', $pgdata, 'reload'; -command_fails( +$node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup" ], 'pg_basebackup fails because of WAL configuration'); -open CONF, ">>$tempdir/pgdata/postgresql.conf"; +open CONF, ">>$pgdata/postgresql.conf"; print CONF "max_replication_slots = 10\n"; print CONF "max_wal_senders = 10\n"; print CONF "wal_level = archive\n"; close CONF; -restart_test_server; +$node->restart; -command_ok([ 'pg_basebackup', '-D', "$tempdir/backup" ], +$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backup" ], 'pg_basebackup runs'); ok(-f "$tempdir/backup/PG_VERSION", 'backup was created'); -is_deeply([sort(slurp_dir("$tempdir/backup/pg_xlog/"))], - [sort qw(. .. archive_status)], - 'no WAL files copied'); +is_deeply( + [ sort(slurp_dir("$tempdir/backup/pg_xlog/")) ], + [ sort qw(. .. archive_status) ], + 'no WAL files copied'); -command_ok( +$node->command_ok( [ 'pg_basebackup', '-D', "$tempdir/backup2", '--xlogdir', "$tempdir/xlog2" ], 'separate xlog directory'); ok(-f "$tempdir/backup2/PG_VERSION", 'backup was created'); ok(-d "$tempdir/xlog2/", 'xlog directory was created'); -command_ok([ 'pg_basebackup', '-D', "$tempdir/tarbackup", '-Ft' ], +$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/tarbackup", '-Ft' ], 'tar format'); ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created'); -command_fails( +$node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T=/foo" ], '-T with empty old directory fails'); -command_fails( +$node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=" ], '-T with empty new directory fails'); -command_fails( +$node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=/bar=/baz" ], '-T with multiple = fails'); -command_fails( +$node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-Tfoo=/bar" ], '-T with old directory not absolute fails'); -command_fails( +$node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-T/foo=bar" ], '-T with new directory not absolute fails'); -command_fails( +$node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp', "-Tfoo" ], '-T with invalid format fails'); # Tar format doesn't support filenames longer than 100 bytes. my $superlongname = "superlongname_" . ("x" x 100); -my $superlongpath = "$tempdir/pgdata/$superlongname"; +my $superlongpath = "$pgdata/$superlongname"; open FILE, ">$superlongpath" or die "unable to create file $superlongpath"; close FILE; -command_fails([ 'pg_basebackup', '-D', "$tempdir/tarbackup_l1", '-Ft' ], +$node->command_fails( + [ 'pg_basebackup', '-D', "$tempdir/tarbackup_l1", '-Ft' ], 'pg_basebackup tar with long name fails'); -unlink "$tempdir/pgdata/$superlongname"; +unlink "$pgdata/$superlongname"; # The following tests test symlinks. Windows doesn't have symlinks, so # skip on Windows. -SKIP: { - skip "symlinks not supported on Windows", 10 if ($windows_os); +SKIP: +{ + skip "symlinks not supported on Windows", 10 if ($windows_os); # Create a temporary directory in the system location and symlink it # to our physical temp location. That way we can use shorter names # for the tablespace directories, which hopefully won't run afoul of # the 99 character length limit. - my $shorter_tempdir = tempdir_short . "/tempdir"; + my $shorter_tempdir = TestLib::tempdir_short . "/tempdir"; symlink "$tempdir", $shorter_tempdir; mkdir "$tempdir/tblspc1"; - psql 'postgres', - "CREATE TABLESPACE tblspc1 LOCATION '$shorter_tempdir/tblspc1';"; - psql 'postgres', "CREATE TABLE test1 (a int) TABLESPACE tblspc1;"; - command_ok([ 'pg_basebackup', '-D', "$tempdir/tarbackup2", '-Ft' ], - 'tar format with tablespaces'); + $node->psql('postgres', + "CREATE TABLESPACE tblspc1 LOCATION '$shorter_tempdir/tblspc1';"); + $node->psql('postgres', "CREATE TABLE test1 (a int) TABLESPACE tblspc1;"); + $node->command_ok([ 'pg_basebackup', '-D', "$tempdir/tarbackup2", '-Ft' ], + 'tar format with tablespaces'); ok(-f "$tempdir/tarbackup2/base.tar", 'backup tar was created'); my @tblspc_tars = glob "$tempdir/tarbackup2/[0-9]*.tar"; is(scalar(@tblspc_tars), 1, 'one tablespace tar was created'); - command_fails( + $node->command_fails( [ 'pg_basebackup', '-D', "$tempdir/backup1", '-Fp' ], 'plain format with tablespaces fails without tablespace mapping'); - command_ok( + $node->command_ok( [ 'pg_basebackup', '-D', "$tempdir/backup1", '-Fp', "-T$shorter_tempdir/tblspc1=$tempdir/tbackup/tblspc1" ], 'plain format with tablespaces succeeds with tablespace mapping'); ok(-d "$tempdir/tbackup/tblspc1", 'tablespace was relocated'); - opendir(my $dh, "$tempdir/pgdata/pg_tblspc") or die; + opendir(my $dh, "$pgdata/pg_tblspc") or die; ok( ( grep { - -l "$tempdir/backup1/pg_tblspc/$_" - and readlink "$tempdir/backup1/pg_tblspc/$_" eq - "$tempdir/tbackup/tblspc1" - } readdir($dh)), + -l "$tempdir/backup1/pg_tblspc/$_" + and readlink "$tempdir/backup1/pg_tblspc/$_" eq + "$tempdir/tbackup/tblspc1" + } readdir($dh)), "tablespace symlink was updated"); closedir $dh; mkdir "$tempdir/tbl=spc2"; - psql 'postgres', "DROP TABLE test1;"; - psql 'postgres', "DROP TABLESPACE tblspc1;"; - psql 'postgres', - "CREATE TABLESPACE tblspc2 LOCATION '$shorter_tempdir/tbl=spc2';"; - command_ok( + $node->psql('postgres', "DROP TABLE test1;"); + $node->psql('postgres', "DROP TABLESPACE tblspc1;"); + $node->psql('postgres', + "CREATE TABLESPACE tblspc2 LOCATION '$shorter_tempdir/tbl=spc2';"); + $node->command_ok( [ 'pg_basebackup', '-D', "$tempdir/backup3", '-Fp', "-T$shorter_tempdir/tbl\\=spc2=$tempdir/tbackup/tbl\\=spc2" ], 'mapping tablespace with = sign in path'); - ok(-d "$tempdir/tbackup/tbl=spc2", 'tablespace with = sign was relocated'); - psql 'postgres', "DROP TABLESPACE tblspc2;"; + ok(-d "$tempdir/tbackup/tbl=spc2", + 'tablespace with = sign was relocated'); + $node->psql('postgres', "DROP TABLESPACE tblspc2;"); mkdir "$tempdir/$superlongname"; - psql 'postgres', - "CREATE TABLESPACE tblspc3 LOCATION '$tempdir/$superlongname';"; - command_ok([ 'pg_basebackup', '-D', "$tempdir/tarbackup_l3", '-Ft' ], - 'pg_basebackup tar with long symlink target'); - psql 'postgres', "DROP TABLESPACE tblspc3;"; + $node->psql('postgres', + "CREATE TABLESPACE tblspc3 LOCATION '$tempdir/$superlongname';"); + $node->command_ok( + [ 'pg_basebackup', '-D', "$tempdir/tarbackup_l3", '-Ft' ], + 'pg_basebackup tar with long symlink target'); + $node->psql('postgres', "DROP TABLESPACE tblspc3;"); } -command_ok([ 'pg_basebackup', '-D', "$tempdir/backupR", '-R' ], +$node->command_ok([ 'pg_basebackup', '-D', "$tempdir/backupR", '-R' ], 'pg_basebackup -R runs'); ok(-f "$tempdir/backupR/recovery.conf", 'recovery.conf was created'); my $recovery_conf = slurp_file "$tempdir/backupR/recovery.conf"; + # using a character class for the final "'" here works around an apparent # bug in several version of the Msys DTK perl -like($recovery_conf, qr/^standby_mode = 'on[']$/m, 'recovery.conf sets standby_mode'); -like($recovery_conf, qr/^primary_conninfo = '.*port=$ENV{PGPORT}.*'$/m, 'recovery.conf sets primary_conninfo'); +like( + $recovery_conf, + qr/^standby_mode = 'on[']$/m, + 'recovery.conf sets standby_mode'); +like( + $recovery_conf, + qr/^primary_conninfo = '.*port=$ENV{PGPORT}.*'$/m, + 'recovery.conf sets primary_conninfo'); -command_ok([ 'pg_basebackup', '-D', "$tempdir/backupxf", '-X', 'fetch' ], +$node->command_ok( + [ 'pg_basebackup', '-D', "$tempdir/backupxf", '-X', 'fetch' ], 'pg_basebackup -X fetch runs'); -ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxf/pg_xlog")), 'WAL files copied'); -command_ok([ 'pg_basebackup', '-D', "$tempdir/backupxs", '-X', 'stream' ], +ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxf/pg_xlog")), + 'WAL files copied'); +$node->command_ok( + [ 'pg_basebackup', '-D', "$tempdir/backupxs", '-X', 'stream' ], 'pg_basebackup -X stream runs'); -ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxf/pg_xlog")), 'WAL files copied'); +ok(grep(/^[0-9A-F]{24}$/, slurp_dir("$tempdir/backupxf/pg_xlog")), + 'WAL files copied'); -command_fails([ 'pg_basebackup', '-D', "$tempdir/fail", '-S', 'slot1' ], +$node->command_fails( + [ 'pg_basebackup', '-D', "$tempdir/fail", '-S', 'slot1' ], 'pg_basebackup with replication slot fails without -X stream'); -command_fails([ 'pg_basebackup', '-D', "$tempdir/backupxs_sl_fail", '-X', 'stream', '-S', 'slot1' ], +$node->command_fails( + [ 'pg_basebackup', '-D', + "$tempdir/backupxs_sl_fail", '-X', + 'stream', '-S', + 'slot1' ], 'pg_basebackup fails with nonexistent replication slot'); -psql 'postgres', q{SELECT * FROM pg_create_physical_replication_slot('slot1')}; -my $lsn = psql 'postgres', q{SELECT restart_lsn FROM pg_replication_slots WHERE slot_name = 'slot1'}; +$node->psql('postgres', + q{SELECT * FROM pg_create_physical_replication_slot('slot1')}); +my $lsn = $node->psql('postgres', + q{SELECT restart_lsn FROM pg_replication_slots WHERE slot_name = 'slot1'} +); is($lsn, '', 'restart LSN of new slot is null'); -command_ok([ 'pg_basebackup', '-D', "$tempdir/backupxs_sl", '-X', 'stream', '-S', 'slot1' ], +$node->command_ok( + [ 'pg_basebackup', '-D', "$tempdir/backupxs_sl", '-X', + 'stream', '-S', 'slot1' ], 'pg_basebackup -X stream with replication slot runs'); -$lsn = psql 'postgres', q{SELECT restart_lsn FROM pg_replication_slots WHERE slot_name = 'slot1'}; +$lsn = $node->psql('postgres', + q{SELECT restart_lsn FROM pg_replication_slots WHERE slot_name = 'slot1'} +); like($lsn, qr!^0/[0-9A-Z]{7,8}$!, 'restart LSN of slot has advanced'); -command_ok([ 'pg_basebackup', '-D', "$tempdir/backupxs_sl_R", '-X', 'stream', '-S', 'slot1', '-R' ], +$node->command_ok( + [ 'pg_basebackup', '-D', "$tempdir/backupxs_sl_R", '-X', + 'stream', '-S', 'slot1', '-R' ], 'pg_basebackup with replication slot and -R runs'); -like(slurp_file("$tempdir/backupxs_sl_R/recovery.conf"), - qr/^primary_slot_name = 'slot1'$/m, - 'recovery.conf sets primary_slot_name'); +like( + slurp_file("$tempdir/backupxs_sl_R/recovery.conf"), + qr/^primary_slot_name = 'slot1'$/m, + 'recovery.conf sets primary_slot_name'); diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl index e2b0d420a2..ae45f41339 100644 --- a/src/bin/pg_controldata/t/001_pg_controldata.pl +++ b/src/bin/pg_controldata/t/001_pg_controldata.pl @@ -1,16 +1,19 @@ use strict; use warnings; +use PostgresNode; use TestLib; use Test::More tests => 13; -my $tempdir = TestLib::tempdir; - program_help_ok('pg_controldata'); program_version_ok('pg_controldata'); program_options_handling_ok('pg_controldata'); command_fails(['pg_controldata'], 'pg_controldata without arguments fails'); command_fails([ 'pg_controldata', 'nonexistent' ], 'pg_controldata with nonexistent directory fails'); -standard_initdb "$tempdir/data"; -command_like([ 'pg_controldata', "$tempdir/data" ], + +my $node = get_new_node(); +$node->init; +$node->start; + +command_like([ 'pg_controldata', $node->data_dir ], qr/checkpoint/, 'pg_controldata produces output'); diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl index f57abcef1d..cbe99d79ad 100644 --- a/src/bin/pg_ctl/t/001_start_stop.pl +++ b/src/bin/pg_ctl/t/001_start_stop.pl @@ -1,6 +1,8 @@ use strict; use warnings; + use Config; +use PostgresNode; use TestLib; use Test::More tests => 17; @@ -16,13 +18,11 @@ command_exit_is([ 'pg_ctl', 'start', '-D', "$tempdir/nonexistent" ], command_ok([ 'pg_ctl', 'initdb', '-D', "$tempdir/data", '-o', '-N' ], 'pg_ctl initdb'); -command_ok( - [ $ENV{PG_REGRESS}, '--config-auth', - "$tempdir/data" ], +command_ok([ $ENV{PG_REGRESS}, '--config-auth', "$tempdir/data" ], 'configure authentication'); open CONF, ">>$tempdir/data/postgresql.conf"; print CONF "fsync = off\n"; -if (! $windows_os) +if (!$windows_os) { print CONF "listen_addresses = ''\n"; print CONF "unix_socket_directories = '$tempdir_short'\n"; @@ -34,6 +34,7 @@ else close CONF; command_ok([ 'pg_ctl', 'start', '-D', "$tempdir/data", '-w' ], 'pg_ctl start -w'); + # sleep here is because Windows builds can't check postmaster.pid exactly, # so they may mistake a pre-existing postmaster.pid for one created by the # postmaster they start. Waiting more than the 2 seconds slop time allowed diff --git a/src/bin/pg_ctl/t/002_status.pl b/src/bin/pg_ctl/t/002_status.pl index 31f7c722f1..f1c131bd26 100644 --- a/src/bin/pg_ctl/t/002_status.pl +++ b/src/bin/pg_ctl/t/002_status.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 3; @@ -9,14 +11,15 @@ my $tempdir_short = TestLib::tempdir_short; command_exit_is([ 'pg_ctl', 'status', '-D', "$tempdir/nonexistent" ], 4, 'pg_ctl status with nonexistent directory'); -standard_initdb "$tempdir/data"; +my $node = get_new_node(); +$node->init; -command_exit_is([ 'pg_ctl', 'status', '-D', "$tempdir/data" ], +command_exit_is([ 'pg_ctl', 'status', '-D', $node->data_dir ], 3, 'pg_ctl status with server not running'); system_or_bail 'pg_ctl', '-l', "$tempdir/logfile", '-D', - "$tempdir/data", '-w', 'start'; -command_exit_is([ 'pg_ctl', 'status', '-D', "$tempdir/data" ], + $node->data_dir, '-w', 'start'; +command_exit_is([ 'pg_ctl', 'status', '-D', $node->data_dir ], 0, 'pg_ctl status with server running'); -system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data", '-m', 'fast'; +system_or_bail 'pg_ctl', 'stop', '-D', $node->data_dir, '-m', 'fast'; diff --git a/src/bin/pg_rewind/RewindTest.pm b/src/bin/pg_rewind/RewindTest.pm index a4c17371dc..c1c7d1fa19 100644 --- a/src/bin/pg_rewind/RewindTest.pm +++ b/src/bin/pg_rewind/RewindTest.pm @@ -9,22 +9,20 @@ package RewindTest; # To run a test, the test script (in t/ subdirectory) calls the functions # in this module. These functions should be called in this sequence: # -# 1. init_rewind_test - sets up log file etc. +# 1. setup_cluster - creates a PostgreSQL cluster that runs as the master # -# 2. setup_cluster - creates a PostgreSQL cluster that runs as the master +# 2. start_master - starts the master server # -# 3. start_master - starts the master server -# -# 4. create_standby - runs pg_basebackup to initialize a standby server, and +# 3. create_standby - runs pg_basebackup to initialize a standby server, and # sets it up to follow the master. # -# 5. promote_standby - runs "pg_ctl promote" to promote the standby server. +# 4. promote_standby - runs "pg_ctl promote" to promote the standby server. # The old master keeps running. # -# 6. run_pg_rewind - stops the old master (if it's still running) and runs +# 5. run_pg_rewind - stops the old master (if it's still running) and runs # pg_rewind to synchronize it with the now-promoted standby server. # -# 7. clean_rewind_test - stops both servers used in the test, if they're +# 6. clean_rewind_test - stops both servers used in the test, if they're # still running. # # The test script can use the helper functions master_psql and standby_psql @@ -37,27 +35,23 @@ package RewindTest; use strict; use warnings; +use Config; +use Exporter 'import'; +use File::Copy; +use File::Path qw(rmtree); +use IPC::Run qw(run); +use PostgresNode; use TestLib; use Test::More; -use Config; -use File::Copy; -use File::Path qw(rmtree); -use IPC::Run qw(run start); - -use Exporter 'import'; our @EXPORT = qw( - $connstr_master - $connstr_standby - $test_master_datadir - $test_standby_datadir + $node_master + $node_standby - append_to_file master_psql standby_psql check_query - init_rewind_test setup_cluster start_master create_standby @@ -66,32 +60,24 @@ our @EXPORT = qw( clean_rewind_test ); -our $test_master_datadir = "$tmp_check/data_master"; -our $test_standby_datadir = "$tmp_check/data_standby"; - -# Define non-conflicting ports for both nodes. -my $port_master = $ENV{PGPORT}; -my $port_standby = $port_master + 1; - -my $connstr_master = "port=$port_master"; -my $connstr_standby = "port=$port_standby"; - -$ENV{PGDATABASE} = "postgres"; +# Our nodes. +our $node_master; +our $node_standby; sub master_psql { my $cmd = shift; - system_or_bail 'psql', '-q', '--no-psqlrc', '-d', $connstr_master, - '-c', "$cmd"; + system_or_bail 'psql', '-q', '--no-psqlrc', '-d', + $node_master->connstr('postgres'), '-c', "$cmd"; } sub standby_psql { my $cmd = shift; - system_or_bail 'psql', '-q', '--no-psqlrc', '-d', $connstr_standby, - '-c', "$cmd"; + system_or_bail 'psql', '-q', '--no-psqlrc', '-d', + $node_standby->connstr('postgres'), '-c', "$cmd"; } # Run a query against the master, and check that the output matches what's @@ -103,8 +89,9 @@ sub check_query # we want just the output, no formatting my $result = run [ - 'psql', '-q', '-A', '-t', '--no-psqlrc', '-d', - $connstr_master, '-c', $query ], + 'psql', '-q', '-A', '-t', '--no-psqlrc', '-d', + $node_master->connstr('postgres'), + '-c', $query ], '>', \$stdout, '2>', \$stderr; # We don't use ok() for the exit code and stderr, because we want this @@ -125,56 +112,16 @@ sub check_query } } -# Run a query once a second, until it returns 't' (i.e. SQL boolean true). -sub poll_query_until -{ - my ($query, $connstr) = @_; - - my $max_attempts = 30; - my $attempts = 0; - my ($stdout, $stderr); - - while ($attempts < $max_attempts) - { - my $cmd = [ 'psql', '-At', '-c', "$query", '-d', "$connstr" ]; - my $result = run $cmd, '>', \$stdout, '2>', \$stderr; - - chomp($stdout); - $stdout =~ s/\r//g if $Config{osname} eq 'msys'; - if ($stdout eq "t") - { - return 1; - } - - # Wait a second before retrying. - sleep 1; - $attempts++; - } - - # The query result didn't change in 30 seconds. Give up. Print the stderr - # from the last attempt, hopefully that's useful for debugging. - diag $stderr; - return 0; -} - -sub append_to_file -{ - my ($filename, $str) = @_; - - open my $fh, ">>", $filename or die "could not open file $filename"; - print $fh $str; - close $fh; -} - sub setup_cluster { + # Initialize master, data checksums are mandatory - rmtree($test_master_datadir); - standard_initdb($test_master_datadir); + $node_master = get_new_node(); + $node_master->init; # Custom parameters for master's postgresql.conf - append_to_file( - "$test_master_datadir/postgresql.conf", qq( + $node_master->append_conf( + "postgresql.conf", qq( wal_level = hot_standby max_wal_senders = 2 wal_keep_segments = 20 @@ -185,17 +132,11 @@ hot_standby = on autovacuum = off max_connections = 10 )); - - # Accept replication connections on master - configure_hba_for_replication $test_master_datadir; } sub start_master { - system_or_bail('pg_ctl' , '-w', - '-D' , $test_master_datadir, - '-l', "$log_path/master.log", - "-o", "-p $port_master", 'start'); + $node_master->start; #### Now run the test-specific parts to initialize the master before setting # up standby @@ -203,24 +144,20 @@ sub start_master sub create_standby { + $node_standby = get_new_node(); + $node_master->backup('my_backup'); + $node_standby->init_from_backup($node_master, 'my_backup'); + my $connstr_master = $node_master->connstr('postgres'); - # Set up standby with necessary parameter - rmtree $test_standby_datadir; - - # Base backup is taken with xlog files included - system_or_bail('pg_basebackup', '-D', $test_standby_datadir, - '-p', $port_master, '-x'); - append_to_file( - "$test_standby_datadir/recovery.conf", qq( + $node_standby->append_conf( + "recovery.conf", qq( primary_conninfo='$connstr_master application_name=rewind_standby' standby_mode=on recovery_target_timeline='latest' )); # Start standby - system_or_bail('pg_ctl', '-w', '-D', $test_standby_datadir, - '-l', "$log_path/standby.log", - '-o', "-p $port_standby", 'start'); + $node_standby->start; # The standby may have WAL to apply before it matches the primary. That # is fine, because no test examines the standby before promotion. @@ -234,14 +171,15 @@ sub promote_standby # Wait for the standby to receive and write all WAL. my $wal_received_query = "SELECT pg_current_xlog_location() = write_location FROM pg_stat_replication WHERE application_name = 'rewind_standby';"; - poll_query_until($wal_received_query, $connstr_master) + $node_master->poll_query_until('postgres', $wal_received_query) or die "Timed out while waiting for standby to receive and write WAL"; # Now promote slave and insert some new data on master, this will put # the master out-of-sync with the standby. Wait until the standby is # out of recovery mode, and is ready to accept read-write connections. - system_or_bail('pg_ctl', '-w', '-D', $test_standby_datadir, 'promote'); - poll_query_until("SELECT NOT pg_is_in_recovery()", $connstr_standby) + system_or_bail('pg_ctl', '-w', '-D', $node_standby->data_dir, 'promote'); + $node_standby->poll_query_until('postgres', + "SELECT NOT pg_is_in_recovery()") or die "Timed out while waiting for promotion of standby"; # Force a checkpoint after the promotion. pg_rewind looks at the control @@ -255,10 +193,14 @@ sub promote_standby sub run_pg_rewind { - my $test_mode = shift; + my $test_mode = shift; + my $master_pgdata = $node_master->data_dir; + my $standby_pgdata = $node_standby->data_dir; + my $standby_connstr = $node_standby->connstr('postgres'); + my $tmp_folder = TestLib::tempdir; # Stop the master and be ready to perform the rewind - system_or_bail('pg_ctl', '-D', $test_master_datadir, '-m', 'fast', 'stop'); + $node_master->stop; # At this point, the rewind processing is ready to run. # We now have a very simple scenario with a few diverged WAL record. @@ -267,31 +209,33 @@ sub run_pg_rewind # Keep a temporary postgresql.conf for master node or it would be # overwritten during the rewind. - copy("$test_master_datadir/postgresql.conf", - "$tmp_check/master-postgresql.conf.tmp"); + copy( + "$master_pgdata/postgresql.conf", + "$tmp_folder/master-postgresql.conf.tmp"); # Now run pg_rewind if ($test_mode eq "local") { + # Do rewind using a local pgdata as source # Stop the master and be ready to perform the rewind - system_or_bail('pg_ctl', '-D', $test_standby_datadir, - '-m', 'fast', 'stop'); - command_ok(['pg_rewind', - "--debug", - "--source-pgdata=$test_standby_datadir", - "--target-pgdata=$test_master_datadir"], - 'pg_rewind local'); + $node_standby->stop; + command_ok( + [ 'pg_rewind', + "--debug", + "--source-pgdata=$standby_pgdata", + "--target-pgdata=$master_pgdata" ], + 'pg_rewind local'); } elsif ($test_mode eq "remote") { + # Do rewind using a remote connection as source - command_ok(['pg_rewind', - "--debug", - "--source-server", - "port=$port_standby dbname=postgres", - "--target-pgdata=$test_master_datadir"], - 'pg_rewind remote'); + command_ok( + [ 'pg_rewind', "--debug", + "--source-server", $standby_connstr, + "--target-pgdata=$master_pgdata" ], + 'pg_rewind remote'); } else { @@ -301,21 +245,21 @@ sub run_pg_rewind } # Now move back postgresql.conf with old settings - move("$tmp_check/master-postgresql.conf.tmp", - "$test_master_datadir/postgresql.conf"); + move( + "$tmp_folder/master-postgresql.conf.tmp", + "$master_pgdata/postgresql.conf"); # Plug-in rewound node to the now-promoted standby node - append_to_file( - "$test_master_datadir/recovery.conf", qq( + my $port_standby = $node_standby->port; + $node_master->append_conf( + 'recovery.conf', qq( primary_conninfo='port=$port_standby' standby_mode=on recovery_target_timeline='latest' )); # Restart the master to check that rewind went correctly - system_or_bail('pg_ctl', '-w', '-D', $test_master_datadir, - '-l', "$log_path/master.log", - '-o', "-p $port_master", 'start'); + $node_master->start; #### Now run the test-specific parts to check the result } @@ -323,22 +267,8 @@ recovery_target_timeline='latest' # Clean up after the test. Stop both servers, if they're still running. sub clean_rewind_test { - if ($test_master_datadir) - { - system - 'pg_ctl', '-D', $test_master_datadir, '-m', 'immediate', 'stop'; - } - if ($test_standby_datadir) - { - system - 'pg_ctl', '-D', $test_standby_datadir, '-m', 'immediate', 'stop'; - } + $node_master->teardown_node if defined $node_master; + $node_standby->teardown_node if defined $node_standby; } -# Stop the test servers, just in case they're still running. -END -{ - my $save_rc = $?; - clean_rewind_test(); - $? = $save_rc; -} +1; diff --git a/src/bin/pg_rewind/t/003_extrafiles.pl b/src/bin/pg_rewind/t/003_extrafiles.pl index d317f53186..cedde1409b 100644 --- a/src/bin/pg_rewind/t/003_extrafiles.pl +++ b/src/bin/pg_rewind/t/003_extrafiles.pl @@ -17,7 +17,7 @@ sub run_test RewindTest::setup_cluster(); RewindTest::start_master(); - my $test_master_datadir = $RewindTest::test_master_datadir; + my $test_master_datadir = $node_master->data_dir; # Create a subdir and files that will be present in both mkdir "$test_master_datadir/tst_both_dir"; @@ -30,6 +30,7 @@ sub run_test RewindTest::create_standby(); # Create different subdirs and files in master and standby + my $test_standby_datadir = $node_standby->data_dir; mkdir "$test_standby_datadir/tst_standby_dir"; append_to_file "$test_standby_datadir/tst_standby_dir/standby_file1", diff --git a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl index c5f72e2e3c..bdcab5688b 100644 --- a/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl +++ b/src/bin/pg_rewind/t/004_pg_xlog_symlink.pl @@ -23,11 +23,13 @@ sub run_test { my $test_mode = shift; - my $master_xlogdir = "$tmp_check/xlog_master"; + my $master_xlogdir = "${TestLib::tmp_check}/xlog_master"; rmtree($master_xlogdir); RewindTest::setup_cluster(); + my $test_master_datadir = $node_master->data_dir; + # turn pg_xlog into a symlink print("moving $test_master_datadir/pg_xlog to $master_xlogdir\n"); move("$test_master_datadir/pg_xlog", $master_xlogdir) or die; diff --git a/src/bin/scripts/t/010_clusterdb.pl b/src/bin/scripts/t/010_clusterdb.pl index dc0d78a27d..5131b35d82 100644 --- a/src/bin/scripts/t/010_clusterdb.pl +++ b/src/bin/scripts/t/010_clusterdb.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 13; @@ -7,20 +9,22 @@ program_help_ok('clusterdb'); program_version_ok('clusterdb'); program_options_handling_ok('clusterdb'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -issues_sql_like( - [ 'clusterdb', 'postgres' ], +$node->issues_sql_like( + ['clusterdb'], qr/statement: CLUSTER;/, 'SQL CLUSTER run'); -command_fails([ 'clusterdb', '-t', 'nonexistent', 'postgres' ], +$node->command_fails([ 'clusterdb', '-t', 'nonexistent' ], 'fails with nonexistent table'); -psql 'postgres', -'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a); CLUSTER test1 USING test1x'; -issues_sql_like( - [ 'clusterdb', '-t', 'test1', 'postgres' ], +$node->psql('postgres', +'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a); CLUSTER test1 USING test1x' +); +$node->issues_sql_like( + [ 'clusterdb', '-t', 'test1' ], qr/statement: CLUSTER test1;/, 'cluster specific table'); diff --git a/src/bin/scripts/t/011_clusterdb_all.pl b/src/bin/scripts/t/011_clusterdb_all.pl index 7769f70bb1..15cd30cf4a 100644 --- a/src/bin/scripts/t/011_clusterdb_all.pl +++ b/src/bin/scripts/t/011_clusterdb_all.pl @@ -1,12 +1,19 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 2; -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -issues_sql_like( +# clusterdb -a is not compatible with -d, hence enforce environment variable +# correctly. +$ENV{PGDATABASE} = 'postgres'; + +$node->issues_sql_like( [ 'clusterdb', '-a' ], qr/statement: CLUSTER.*statement: CLUSTER/s, 'cluster all databases'); diff --git a/src/bin/scripts/t/020_createdb.pl b/src/bin/scripts/t/020_createdb.pl index a44283c945..e0cf860058 100644 --- a/src/bin/scripts/t/020_createdb.pl +++ b/src/bin/scripts/t/020_createdb.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 13; @@ -7,16 +9,18 @@ program_help_ok('createdb'); program_version_ok('createdb'); program_options_handling_ok('createdb'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -issues_sql_like( +$node->issues_sql_like( [ 'createdb', 'foobar1' ], qr/statement: CREATE DATABASE foobar1/, 'SQL CREATE DATABASE run'); -issues_sql_like( +$node->issues_sql_like( [ 'createdb', '-l', 'C', '-E', 'LATIN1', '-T', 'template0', 'foobar2' ], qr/statement: CREATE DATABASE foobar2 ENCODING 'LATIN1'/, 'create database with encoding'); -command_fails([ 'createdb', 'foobar1' ], 'fails if database already exists'); +$node->command_fails([ 'createdb', 'foobar1' ], + 'fails if database already exists'); diff --git a/src/bin/scripts/t/030_createlang.pl b/src/bin/scripts/t/030_createlang.pl index 7ff0a3ed38..4097f03da9 100644 --- a/src/bin/scripts/t/030_createlang.pl +++ b/src/bin/scripts/t/030_createlang.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 14; @@ -7,18 +9,17 @@ program_help_ok('createlang'); program_version_ok('createlang'); program_options_handling_ok('createlang'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -command_fails( - [ 'createlang', 'plpgsql', 'postgres' ], +$node->command_fails([ 'createlang', 'plpgsql' ], 'fails if language already exists'); -psql 'postgres', 'DROP EXTENSION plpgsql'; -issues_sql_like( - [ 'createlang', 'plpgsql', 'postgres' ], +$node->psql('postgres', 'DROP EXTENSION plpgsql'); +$node->issues_sql_like( + [ 'createlang', 'plpgsql' ], qr/statement: CREATE EXTENSION "plpgsql"/, 'SQL CREATE EXTENSION run'); -command_like([ 'createlang', '--list', 'postgres' ], - qr/plpgsql/, 'list output'); +$node->command_like([ 'createlang', '--list' ], qr/plpgsql/, 'list output'); diff --git a/src/bin/scripts/t/040_createuser.pl b/src/bin/scripts/t/040_createuser.pl index 4d44e14b7c..fcada6338c 100644 --- a/src/bin/scripts/t/040_createuser.pl +++ b/src/bin/scripts/t/040_createuser.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 17; @@ -7,24 +9,26 @@ program_help_ok('createuser'); program_version_ok('createuser'); program_options_handling_ok('createuser'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -issues_sql_like( +$node->issues_sql_like( [ 'createuser', 'user1' ], qr/statement: CREATE ROLE user1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN;/, 'SQL CREATE USER run'); -issues_sql_like( +$node->issues_sql_like( [ 'createuser', '-L', 'role1' ], qr/statement: CREATE ROLE role1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN;/, 'create a non-login role'); -issues_sql_like( +$node->issues_sql_like( [ 'createuser', '-r', 'user2' ], qr/statement: CREATE ROLE user2 NOSUPERUSER NOCREATEDB CREATEROLE INHERIT LOGIN;/, 'create a CREATEROLE user'); -issues_sql_like( +$node->issues_sql_like( [ 'createuser', '-s', 'user3' ], qr/statement: CREATE ROLE user3 SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;/, 'create a superuser'); -command_fails([ 'createuser', 'user1' ], 'fails if role already exists'); +$node->command_fails([ 'createuser', 'user1' ], + 'fails if role already exists'); diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl index 3065e5051d..2adc80a03f 100644 --- a/src/bin/scripts/t/050_dropdb.pl +++ b/src/bin/scripts/t/050_dropdb.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 11; @@ -7,13 +9,15 @@ program_help_ok('dropdb'); program_version_ok('dropdb'); program_options_handling_ok('dropdb'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -psql 'postgres', 'CREATE DATABASE foobar1'; -issues_sql_like( +$node->psql('postgres', 'CREATE DATABASE foobar1'); +$node->issues_sql_like( [ 'dropdb', 'foobar1' ], qr/statement: DROP DATABASE foobar1/, 'SQL DROP DATABASE run'); -command_fails([ 'dropdb', 'nonexistent' ], 'fails with nonexistent database'); +$node->command_fails([ 'dropdb', 'nonexistent' ], + 'fails with nonexistent database'); diff --git a/src/bin/scripts/t/060_droplang.pl b/src/bin/scripts/t/060_droplang.pl index 6a21d7e33d..722804747e 100644 --- a/src/bin/scripts/t/060_droplang.pl +++ b/src/bin/scripts/t/060_droplang.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 11; @@ -7,14 +9,15 @@ program_help_ok('droplang'); program_version_ok('droplang'); program_options_handling_ok('droplang'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -issues_sql_like( +$node->issues_sql_like( [ 'droplang', 'plpgsql', 'postgres' ], qr/statement: DROP EXTENSION "plpgsql"/, 'SQL DROP EXTENSION run'); -command_fails( +$node->command_fails( [ 'droplang', 'nonexistent', 'postgres' ], 'fails with nonexistent language'); diff --git a/src/bin/scripts/t/070_dropuser.pl b/src/bin/scripts/t/070_dropuser.pl index bbb3b7922a..0849f77ed6 100644 --- a/src/bin/scripts/t/070_dropuser.pl +++ b/src/bin/scripts/t/070_dropuser.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 11; @@ -7,13 +9,15 @@ program_help_ok('dropuser'); program_version_ok('dropuser'); program_options_handling_ok('dropuser'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -psql 'postgres', 'CREATE ROLE foobar1'; -issues_sql_like( +$node->psql('postgres', 'CREATE ROLE foobar1'); +$node->issues_sql_like( [ 'dropuser', 'foobar1' ], qr/statement: DROP ROLE foobar1/, 'SQL DROP ROLE run'); -command_fails([ 'dropuser', 'nonexistent' ], 'fails with nonexistent user'); +$node->command_fails([ 'dropuser', 'nonexistent' ], + 'fails with nonexistent user'); diff --git a/src/bin/scripts/t/080_pg_isready.pl b/src/bin/scripts/t/080_pg_isready.pl index f432505d5c..8f3f25cc36 100644 --- a/src/bin/scripts/t/080_pg_isready.pl +++ b/src/bin/scripts/t/080_pg_isready.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 10; @@ -9,7 +11,8 @@ program_options_handling_ok('pg_isready'); command_fails(['pg_isready'], 'fails with no server running'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -command_ok(['pg_isready'], 'succeeds with server running'); +$node->command_ok(['pg_isready'], 'succeeds with server running'); diff --git a/src/bin/scripts/t/090_reindexdb.pl b/src/bin/scripts/t/090_reindexdb.pl index 42628c25e2..fd4eac347e 100644 --- a/src/bin/scripts/t/090_reindexdb.pl +++ b/src/bin/scripts/t/090_reindexdb.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 20; @@ -7,35 +9,36 @@ program_help_ok('reindexdb'); program_version_ok('reindexdb'); program_options_handling_ok('reindexdb'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; $ENV{PGOPTIONS} = '--client-min-messages=WARNING'; -issues_sql_like( +$node->issues_sql_like( [ 'reindexdb', 'postgres' ], qr/statement: REINDEX DATABASE postgres;/, 'SQL REINDEX run'); -psql 'postgres', - 'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a);'; -issues_sql_like( +$node->psql('postgres', + 'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a);'); +$node->issues_sql_like( [ 'reindexdb', '-t', 'test1', 'postgres' ], qr/statement: REINDEX TABLE test1;/, 'reindex specific table'); -issues_sql_like( +$node->issues_sql_like( [ 'reindexdb', '-i', 'test1x', 'postgres' ], qr/statement: REINDEX INDEX test1x;/, 'reindex specific index'); -issues_sql_like( +$node->issues_sql_like( [ 'reindexdb', '-S', 'pg_catalog', 'postgres' ], qr/statement: REINDEX SCHEMA pg_catalog;/, 'reindex specific schema'); -issues_sql_like( +$node->issues_sql_like( [ 'reindexdb', '-s', 'postgres' ], qr/statement: REINDEX SYSTEM postgres;/, 'reindex system tables'); -issues_sql_like( +$node->issues_sql_like( [ 'reindexdb', '-v', '-t', 'test1', 'postgres' ], qr/statement: REINDEX \(VERBOSE\) TABLE test1;/, 'reindex with verbose output'); diff --git a/src/bin/scripts/t/091_reindexdb_all.pl b/src/bin/scripts/t/091_reindexdb_all.pl index ffadf29bc6..d47b18b989 100644 --- a/src/bin/scripts/t/091_reindexdb_all.pl +++ b/src/bin/scripts/t/091_reindexdb_all.pl @@ -1,14 +1,16 @@ use strict; use warnings; -use TestLib; + +use PostgresNode; use Test::More tests => 2; -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; $ENV{PGOPTIONS} = '--client-min-messages=WARNING'; -issues_sql_like( +$node->issues_sql_like( [ 'reindexdb', '-a' ], qr/statement: REINDEX.*statement: REINDEX/s, 'reindex all databases'); diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl index ac160ba837..387d2b41e2 100644 --- a/src/bin/scripts/t/100_vacuumdb.pl +++ b/src/bin/scripts/t/100_vacuumdb.pl @@ -1,5 +1,7 @@ use strict; use warnings; + +use PostgresNode; use TestLib; use Test::More tests => 18; @@ -7,26 +9,27 @@ program_help_ok('vacuumdb'); program_version_ok('vacuumdb'); program_options_handling_ok('vacuumdb'); -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -issues_sql_like( +$node->issues_sql_like( [ 'vacuumdb', 'postgres' ], qr/statement: VACUUM;/, 'SQL VACUUM run'); -issues_sql_like( +$node->issues_sql_like( [ 'vacuumdb', '-f', 'postgres' ], qr/statement: VACUUM \(FULL\);/, 'vacuumdb -f'); -issues_sql_like( +$node->issues_sql_like( [ 'vacuumdb', '-F', 'postgres' ], qr/statement: VACUUM \(FREEZE\);/, 'vacuumdb -F'); -issues_sql_like( +$node->issues_sql_like( [ 'vacuumdb', '-z', 'postgres' ], qr/statement: VACUUM \(ANALYZE\);/, 'vacuumdb -z'); -issues_sql_like( +$node->issues_sql_like( [ 'vacuumdb', '-Z', 'postgres' ], qr/statement: ANALYZE;/, 'vacuumdb -Z'); diff --git a/src/bin/scripts/t/101_vacuumdb_all.pl b/src/bin/scripts/t/101_vacuumdb_all.pl index e90f321d1e..8f1536f44f 100644 --- a/src/bin/scripts/t/101_vacuumdb_all.pl +++ b/src/bin/scripts/t/101_vacuumdb_all.pl @@ -1,12 +1,14 @@ use strict; use warnings; -use TestLib; + +use PostgresNode; use Test::More tests => 2; -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -issues_sql_like( +$node->issues_sql_like( [ 'vacuumdb', '-a' ], qr/statement: VACUUM.*statement: VACUUM/s, 'vacuum all databases'); diff --git a/src/bin/scripts/t/102_vacuumdb_stages.pl b/src/bin/scripts/t/102_vacuumdb_stages.pl index 57b980ec6a..4cb5b64877 100644 --- a/src/bin/scripts/t/102_vacuumdb_stages.pl +++ b/src/bin/scripts/t/102_vacuumdb_stages.pl @@ -1,12 +1,14 @@ use strict; use warnings; -use TestLib; + +use PostgresNode; use Test::More tests => 4; -my $tempdir = tempdir; -start_test_server $tempdir; +my $node = get_new_node(); +$node->init; +$node->start; -issues_sql_like( +$node->issues_sql_like( [ 'vacuumdb', '--analyze-in-stages', 'postgres' ], qr/.*statement:\ SET\ default_statistics_target=1;\ SET\ vacuum_cost_delay=0; .*statement:\ ANALYZE.* @@ -16,8 +18,7 @@ qr/.*statement:\ SET\ default_statistics_target=1;\ SET\ vacuum_cost_delay=0; .*statement:\ ANALYZE/sx, 'analyze three times'); - -issues_sql_like( +$node->issues_sql_like( [ 'vacuumdb', '--analyze-in-stages', '--all' ], qr/.*statement:\ SET\ default_statistics_target=1;\ SET\ vacuum_cost_delay=0; .*statement:\ ANALYZE.* diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm new file mode 100644 index 0000000000..aa7a00c9fa --- /dev/null +++ b/src/test/perl/PostgresNode.pm @@ -0,0 +1,470 @@ +# PostgresNode, class representing a data directory and postmaster. +# +# This contains a basic set of routines able to work on a PostgreSQL node, +# allowing to start, stop, backup and initialize it with various options. +# The set of nodes managed by a given test is also managed by this module. + +package PostgresNode; + +use strict; +use warnings; + +use Config; +use Cwd; +use Exporter 'import'; +use File::Basename; +use File::Spec; +use File::Temp (); +use IPC::Run; +use PostgresNode; +use RecursiveCopy; +use Test::More; +use TestLib (); + +our @EXPORT = qw( + get_new_node +); + +our ($test_pghost, $last_port_assigned, @all_nodes); + +BEGIN +{ + + # PGHOST is set once and for all through a single series of tests when + # this module is loaded. + $test_pghost = + $TestLib::windows_os ? "127.0.0.1" : TestLib::tempdir_short; + $ENV{PGHOST} = $test_pghost; + $ENV{PGDATABASE} = 'postgres'; + + # Tracking of last port value assigned to accelerate free port lookup. + # XXX: Should this use PG_VERSION_NUM? + $last_port_assigned = 90600 % 16384 + 49152; + + # Node tracking + @all_nodes = (); +} + +sub new +{ + my $class = shift; + my $pghost = shift; + my $pgport = shift; + my $self = { + _port => $pgport, + _host => $pghost, + _basedir => TestLib::tempdir, + _applname => "node_$pgport", + _logfile => "$TestLib::log_path/node_$pgport.log" }; + + bless $self, $class; + $self->dump_info; + + return $self; +} + +sub port +{ + my ($self) = @_; + return $self->{_port}; +} + +sub host +{ + my ($self) = @_; + return $self->{_host}; +} + +sub basedir +{ + my ($self) = @_; + return $self->{_basedir}; +} + +sub applname +{ + my ($self) = @_; + return $self->{_applname}; +} + +sub logfile +{ + my ($self) = @_; + return $self->{_logfile}; +} + +sub connstr +{ + my ($self, $dbname) = @_; + my $pgport = $self->port; + my $pghost = $self->host; + if (!defined($dbname)) + { + return "port=$pgport host=$pghost"; + } + return "port=$pgport host=$pghost dbname=$dbname"; +} + +sub data_dir +{ + my ($self) = @_; + my $res = $self->basedir; + return "$res/pgdata"; +} + +sub archive_dir +{ + my ($self) = @_; + my $basedir = $self->basedir; + return "$basedir/archives"; +} + +sub backup_dir +{ + my ($self) = @_; + my $basedir = $self->basedir; + return "$basedir/backup"; +} + +# Dump node information +sub dump_info +{ + my ($self) = @_; + print "Data directory: " . $self->data_dir . "\n"; + print "Backup directory: " . $self->backup_dir . "\n"; + print "Archive directory: " . $self->archive_dir . "\n"; + print "Connection string: " . $self->connstr . "\n"; + print "Application name: " . $self->applname . "\n"; + print "Log file: " . $self->logfile . "\n"; +} + +sub set_replication_conf +{ + my ($self) = @_; + my $pgdata = $self->data_dir; + + open my $hba, ">>$pgdata/pg_hba.conf"; + print $hba "\n# Allow replication (set up by PostgresNode.pm)\n"; + if (!$TestLib::windows_os) + { + print $hba "local replication all trust\n"; + } + else + { + print $hba +"host replication all 127.0.0.1/32 sspi include_realm=1 map=regress\n"; + } + close $hba; +} + +# Initialize a new cluster for testing. +# +# Authentication is set up so that only the current OS user can access the +# cluster. On Unix, we use Unix domain socket connections, with the socket in +# a directory that's only accessible to the current user to ensure that. +# On Windows, we use SSPI authentication to ensure the same (by pg_regress +# --config-auth). +sub init +{ + my ($self, %params) = @_; + my $port = $self->port; + my $pgdata = $self->data_dir; + my $host = $self->host; + + $params{hba_permit_replication} = 1 + if (!defined($params{hba_permit_replication})); + + mkdir $self->backup_dir; + mkdir $self->archive_dir; + + TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N'); + TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata); + + open my $conf, ">>$pgdata/postgresql.conf"; + print $conf "\n# Added by PostgresNode.pm)\n"; + print $conf "fsync = off\n"; + print $conf "log_statement = all\n"; + print $conf "port = $port\n"; + if ($TestLib::windows_os) + { + print $conf "listen_addresses = '$host'\n"; + } + else + { + print $conf "unix_socket_directories = '$host'\n"; + print $conf "listen_addresses = ''\n"; + } + close $conf; + + $self->set_replication_conf if ($params{hba_permit_replication}); +} + +sub append_conf +{ + my ($self, $filename, $str) = @_; + + my $conffile = $self->data_dir . '/' . $filename; + + TestLib::append_to_file($conffile, $str); +} + +sub backup +{ + my ($self, $backup_name) = @_; + my $backup_path = $self->backup_dir . '/' . $backup_name; + my $port = $self->port; + + print "# Taking backup $backup_name from node with port $port\n"; + TestLib::system_or_bail("pg_basebackup -D $backup_path -p $port -x"); + print "# Backup finished\n"; +} + +sub init_from_backup +{ + my ($self, $root_node, $backup_name) = @_; + my $backup_path = $root_node->backup_dir . '/' . $backup_name; + my $port = $self->port; + my $root_port = $root_node->port; + + print +"Initializing node $port from backup \"$backup_name\" of node $root_port\n"; + die "Backup $backup_path does not exist" unless -d $backup_path; + + mkdir $self->backup_dir; + mkdir $self->archive_dir; + + my $data_path = $self->data_dir; + rmdir($data_path); + RecursiveCopy::copypath($backup_path, $data_path); + chmod(0700, $data_path); + + # Base configuration for this node + $self->append_conf( + 'postgresql.conf', + qq( +port = $port +)); + $self->set_replication_conf; +} + +sub start +{ + my ($self) = @_; + my $port = $self->port; + my $pgdata = $self->data_dir; + print("### Starting test server in $pgdata\n"); + my $ret = TestLib::system_log('pg_ctl', '-w', '-D', $self->data_dir, '-l', + $self->logfile, 'start'); + + if ($ret != 0) + { + print "# pg_ctl failed; logfile:\n"; + print TestLib::slurp_file($self->logfile); + BAIL_OUT("pg_ctl failed"); + } + + $self->_update_pid; + +} + +sub stop +{ + my ($self, $mode) = @_; + my $port = $self->port; + my $pgdata = $self->data_dir; + $mode = 'fast' if (!defined($mode)); + print "### Stopping node in $pgdata with port $port using mode $mode\n"; + TestLib::system_log('pg_ctl', '-D', $pgdata, '-m', $mode, 'stop'); + $self->{_pid} = undef; + $self->_update_pid; +} + +sub restart +{ + my ($self) = @_; + my $port = $self->port; + my $pgdata = $self->data_dir; + my $logfile = $self->logfile; + TestLib::system_log('pg_ctl', '-D', $pgdata, '-w', '-l', $logfile, + 'restart'); + $self->_update_pid; +} + +sub _update_pid +{ + my $self = shift; + + # If we can open the PID file, read its first line and that's the PID we + # want. If the file cannot be opened, presumably the server is not + # running; don't be noisy in that case. + open my $pidfile, $self->data_dir . "/postmaster.pid"; + if (not defined $pidfile) + { + $self->{_pid} = undef; + print "# No postmaster PID\n"; + return; + } + + $self->{_pid} = <$pidfile>; + print "# Postmaster PID is $self->{_pid}\n"; + close $pidfile; +} + +# +# Cluster management functions +# + +# Build a new PostgresNode object, assigning a free port number. +# +# We also register the node, to avoid the port number from being reused +# for another node even when this one is not active. +sub get_new_node +{ + my $found = 0; + my $port = $last_port_assigned; + + while ($found == 0) + { + $port++; + print "# Checking for port $port\n"; + my $devnull = $TestLib::windows_os ? "nul" : "/dev/null"; + if (!TestLib::run_log([ 'pg_isready', '-p', $port ])) + { + $found = 1; + + # Found a potential candidate port number. Check first that it is + # not included in the list of registered nodes. + foreach my $node (@all_nodes) + { + $found = 0 if ($node->port == $port); + } + } + } + + print "# Found free port $port\n"; + + # Lock port number found by creating a new node + my $node = new PostgresNode($test_pghost, $port); + + # Add node to list of nodes + push(@all_nodes, $node); + + # And update port for next time + $last_port_assigned = $port; + + return $node; +} + +sub DESTROY +{ + my $self = shift; + return if not defined $self->{_pid}; + print "# signalling QUIT to $self->{_pid}\n"; + kill 'QUIT', $self->{_pid}; +} + +sub teardown_node +{ + my $self = shift; + + $self->stop('immediate'); +} + +sub psql +{ + my ($self, $dbname, $sql) = @_; + + my ($stdout, $stderr); + print("# Running SQL command: $sql\n"); + + IPC::Run::run [ 'psql', '-XAtq', '-d', $self->connstr($dbname), '-f', + '-' ], '<', \$sql, '>', \$stdout, '2>', \$stderr + or die; + + if ($stderr ne "") + { + print "#### Begin standard error\n"; + print $stderr; + print "#### End standard error\n"; + } + chomp $stdout; + $stdout =~ s/\r//g if $Config{osname} eq 'msys'; + return $stdout; +} + +# Run a query once a second, until it returns 't' (i.e. SQL boolean true). +sub poll_query_until +{ + my ($self, $dbname, $query) = @_; + + my $max_attempts = 30; + my $attempts = 0; + my ($stdout, $stderr); + + while ($attempts < $max_attempts) + { + my $cmd = + [ 'psql', '-At', '-c', $query, '-d', $self->connstr($dbname) ]; + my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr; + + chomp($stdout); + $stdout =~ s/\r//g if $Config{osname} eq 'msys'; + if ($stdout eq "t") + { + return 1; + } + + # Wait a second before retrying. + sleep 1; + $attempts++; + } + + # The query result didn't change in 30 seconds. Give up. Print the stderr + # from the last attempt, hopefully that's useful for debugging. + diag $stderr; + return 0; +} + +sub command_ok +{ + my $self = shift; + + local $ENV{PGPORT} = $self->port; + + TestLib::command_ok(@_); +} + +sub command_fails +{ + my $self = shift; + + local $ENV{PGPORT} = $self->port; + + TestLib::command_fails(@_); +} + +sub command_like +{ + my $self = shift; + + local $ENV{PGPORT} = $self->port; + + TestLib::command_like(@_); +} + +# Run a command on the node, then verify that $expected_sql appears in the +# server log file. +sub issues_sql_like +{ + my ($self, $cmd, $expected_sql, $test_name) = @_; + + local $ENV{PGPORT} = $self->port; + + truncate $self->logfile, 0; + my $result = TestLib::run_log($cmd); + ok($result, "@$cmd exit code 0"); + my $log = TestLib::slurp_file($self->logfile); + like($log, $expected_sql, "$test_name: SQL found in server log"); +} + +1; diff --git a/src/test/perl/RecursiveCopy.pm b/src/test/perl/RecursiveCopy.pm new file mode 100644 index 0000000000..9362aa8959 --- /dev/null +++ b/src/test/perl/RecursiveCopy.pm @@ -0,0 +1,42 @@ +# RecursiveCopy, a simple recursive copy implementation +package RecursiveCopy; + +use strict; +use warnings; + +use File::Basename; +use File::Copy; + +sub copypath +{ + my $srcpath = shift; + my $destpath = shift; + + die "Cannot operate on symlinks" if -l $srcpath or -l $destpath; + + # This source path is a file, simply copy it to destination with the + # same name. + die "Destination path $destpath exists as file" if -f $destpath; + if (-f $srcpath) + { + copy($srcpath, $destpath) + or die "copy $srcpath -> $destpath failed: $!"; + return 1; + } + + die "Destination needs to be a directory" unless -d $srcpath; + mkdir($destpath) or die "mkdir($destpath) failed: $!"; + + # Scan existing source directory and recursively copy everything. + opendir(my $directory, $srcpath) or die "could not opendir($srcpath): $!"; + while (my $entry = readdir($directory)) + { + next if ($entry eq '.' || $entry eq '..'); + RecursiveCopy::copypath("$srcpath/$entry", "$destpath/$entry") + or die "copypath $srcpath/$entry -> $destpath/$entry failed"; + } + closedir($directory); + return 1; +} + +1; diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm index 02533ebde5..af46dc8c7a 100644 --- a/src/test/perl/TestLib.pm +++ b/src/test/perl/TestLib.pm @@ -1,3 +1,10 @@ +# TestLib, low-level routines and actions regression tests. +# +# This module contains a set of routines dedicated to environment setup for +# a PostgreSQL regression test tun, and includes some low-level routines +# aimed at controlling command execution, logging and test functions. This +# module should never depend on any other PostgreSQL regression test modules. + package TestLib; use strict; @@ -5,16 +12,17 @@ use warnings; use Config; use Exporter 'import'; +use File::Basename; +use File::Spec; +use File::Temp (); +use IPC::Run; +use SimpleTee; +use Test::More; + our @EXPORT = qw( - tempdir - tempdir_short - standard_initdb - configure_hba_for_replication - start_test_server - restart_test_server - psql slurp_dir slurp_file + append_to_file system_or_bail system_log run_log @@ -26,88 +34,82 @@ our @EXPORT = qw( program_version_ok program_options_handling_ok command_like - issues_sql_like - $tmp_check - $log_path $windows_os ); -use Cwd; -use File::Basename; -use File::Spec; -use File::Temp (); -use IPC::Run qw(run start); +our ($windows_os, $tmp_check, $log_path, $test_logfile); -use SimpleTee; - -use Test::More; - -our $windows_os = $Config{osname} eq 'MSWin32' || $Config{osname} eq 'msys'; - -# Open log file. For each test, the log file name uses the name of the -# file launching this module, without the .pl suffix. -our ($tmp_check, $log_path); -$tmp_check = $ENV{TESTDIR} ? "$ENV{TESTDIR}/tmp_check" : "tmp_check"; -$log_path = "$tmp_check/log"; -mkdir $tmp_check; -mkdir $log_path; -my $test_logfile = basename($0); -$test_logfile =~ s/\.[^.]+$//; -$test_logfile = "$log_path/regress_log_$test_logfile"; -open TESTLOG, '>', $test_logfile or die "Cannot open STDOUT to logfile: $!"; - -# Hijack STDOUT and STDERR to the log file -open(ORIG_STDOUT, ">&STDOUT"); -open(ORIG_STDERR, ">&STDERR"); -open(STDOUT, ">&TESTLOG"); -open(STDERR, ">&TESTLOG"); - -# The test output (ok ...) needs to be printed to the original STDOUT so -# that the 'prove' program can parse it, and display it to the user in -# real time. But also copy it to the log file, to provide more context -# in the log. -my $builder = Test::More->builder; -my $fh = $builder->output; -tie *$fh, "SimpleTee", *ORIG_STDOUT, *TESTLOG; -$fh = $builder->failure_output; -tie *$fh, "SimpleTee", *ORIG_STDERR, *TESTLOG; - -# Enable auto-flushing for all the file handles. Stderr and stdout are -# redirected to the same file, and buffering causes the lines to appear -# in the log in confusing order. -autoflush STDOUT 1; -autoflush STDERR 1; -autoflush TESTLOG 1; - -# Set to untranslated messages, to be able to compare program output -# with expected strings. -delete $ENV{LANGUAGE}; -delete $ENV{LC_ALL}; -$ENV{LC_MESSAGES} = 'C'; - -delete $ENV{PGCONNECT_TIMEOUT}; -delete $ENV{PGDATA}; -delete $ENV{PGDATABASE}; -delete $ENV{PGHOSTADDR}; -delete $ENV{PGREQUIRESSL}; -delete $ENV{PGSERVICE}; -delete $ENV{PGSSLMODE}; -delete $ENV{PGUSER}; - -if (!$ENV{PGPORT}) +BEGIN { - $ENV{PGPORT} = 65432; + + # Set to untranslated messages, to be able to compare program output + # with expected strings. + delete $ENV{LANGUAGE}; + delete $ENV{LC_ALL}; + $ENV{LC_MESSAGES} = 'C'; + + delete $ENV{PGCONNECT_TIMEOUT}; + delete $ENV{PGDATA}; + delete $ENV{PGDATABASE}; + delete $ENV{PGHOSTADDR}; + delete $ENV{PGREQUIRESSL}; + delete $ENV{PGSERVICE}; + delete $ENV{PGSSLMODE}; + delete $ENV{PGUSER}; + delete $ENV{PGPORT}; + delete $ENV{PGHOST}; + + # Must be set early + $windows_os = $Config{osname} eq 'MSWin32' || $Config{osname} eq 'msys'; } -$ENV{PGPORT} = int($ENV{PGPORT}) % 65536; +INIT +{ + # Determine output directories, and create them. The base path is the + # TESTDIR environment variable, which is normally set by the invoking + # Makefile. + $tmp_check = $ENV{TESTDIR} ? "$ENV{TESTDIR}/tmp_check" : "tmp_check"; + $log_path = "$tmp_check/log"; + + mkdir $tmp_check; + mkdir $log_path; + + # Open the test log file, whose name depends on the test name. + $test_logfile = basename($0); + $test_logfile =~ s/\.[^.]+$//; + $test_logfile = "$log_path/regress_log_$test_logfile"; + open TESTLOG, '>', $test_logfile + or die "could not open STDOUT to logfile \"$test_logfile\": $!"; + + # Hijack STDOUT and STDERR to the log file + open(ORIG_STDOUT, ">&STDOUT"); + open(ORIG_STDERR, ">&STDERR"); + open(STDOUT, ">&TESTLOG"); + open(STDERR, ">&TESTLOG"); + + # The test output (ok ...) needs to be printed to the original STDOUT so + # that the 'prove' program can parse it, and display it to the user in + # real time. But also copy it to the log file, to provide more context + # in the log. + my $builder = Test::More->builder; + my $fh = $builder->output; + tie *$fh, "SimpleTee", *ORIG_STDOUT, *TESTLOG; + $fh = $builder->failure_output; + tie *$fh, "SimpleTee", *ORIG_STDERR, *TESTLOG; + + # Enable auto-flushing for all the file handles. Stderr and stdout are + # redirected to the same file, and buffering causes the lines to appear + # in the log in confusing order. + autoflush STDOUT 1; + autoflush STDERR 1; + autoflush TESTLOG 1; +} # # Helper functions # - - sub tempdir { return File::Temp::tempdir( @@ -124,117 +126,31 @@ sub tempdir_short return File::Temp::tempdir(CLEANUP => 1); } -# Initialize a new cluster for testing. -# -# The PGHOST environment variable is set to connect to the new cluster. -# -# Authentication is set up so that only the current OS user can access the -# cluster. On Unix, we use Unix domain socket connections, with the socket in -# a directory that's only accessible to the current user to ensure that. -# On Windows, we use SSPI authentication to ensure the same (by pg_regress -# --config-auth). -sub standard_initdb +sub system_log { - my $pgdata = shift; - system_or_bail('initdb', '-D', "$pgdata", '-A' , 'trust', '-N'); - system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata); - - my $tempdir_short = tempdir_short; - - open CONF, ">>$pgdata/postgresql.conf"; - print CONF "\n# Added by TestLib.pm)\n"; - print CONF "fsync = off\n"; - if ($windows_os) - { - print CONF "listen_addresses = '127.0.0.1'\n"; - } - else - { - print CONF "unix_socket_directories = '$tempdir_short'\n"; - print CONF "listen_addresses = ''\n"; - } - close CONF; - - $ENV{PGHOST} = $windows_os ? "127.0.0.1" : $tempdir_short; + print("# Running: " . join(" ", @_) . "\n"); + return system(@_); } -# Set up the cluster to allow replication connections, in the same way that -# standard_initdb does for normal connections. -sub configure_hba_for_replication +sub system_or_bail { - my $pgdata = shift; - - open HBA, ">>$pgdata/pg_hba.conf"; - print HBA "\n# Allow replication (set up by TestLib.pm)\n"; - if (! $windows_os) + if (system_log(@_) != 0) { - print HBA "local replication all trust\n"; - } - else - { - print HBA "host replication all 127.0.0.1/32 sspi include_realm=1 map=regress\n"; - } - close HBA; -} - -my ($test_server_datadir, $test_server_logfile); - - -# Initialize a new cluster for testing in given directory, and start it. -sub start_test_server -{ - my ($tempdir) = @_; - my $ret; - - print("### Starting test server in $tempdir\n"); - standard_initdb "$tempdir/pgdata"; - - $ret = system_log('pg_ctl', '-D', "$tempdir/pgdata", '-w', '-l', - "$log_path/postmaster.log", '-o', "--log-statement=all", - 'start'); - - if ($ret != 0) - { - print "# pg_ctl failed; logfile:\n"; - system('cat', "$log_path/postmaster.log"); - BAIL_OUT("pg_ctl failed"); - } - - $test_server_datadir = "$tempdir/pgdata"; - $test_server_logfile = "$log_path/postmaster.log"; -} - -sub restart_test_server -{ - print("### Restarting test server\n"); - system_log('pg_ctl', '-D', $test_server_datadir, '-w', '-l', - $test_server_logfile, 'restart'); -} - -END -{ - if ($test_server_datadir) - { - system_log('pg_ctl', '-D', $test_server_datadir, '-m', - 'immediate', 'stop'); + BAIL_OUT("system $_[0] failed"); } } -sub psql +sub run_log { - my ($dbname, $sql) = @_; - my ($stdout, $stderr); - print("# Running SQL command: $sql\n"); - run [ 'psql', '-X', '-A', '-t', '-q', '-d', $dbname, '-f', '-' ], '<', \$sql, '>', \$stdout, '2>', \$stderr or die; - chomp $stdout; - $stdout =~ s/\r//g if $Config{osname} eq 'msys'; - return $stdout; + print("# Running: " . join(" ", @{ $_[0] }) . "\n"); + return run(@_); } sub slurp_dir { my ($dir) = @_; - opendir(my $dh, $dir) or die; + opendir(my $dh, $dir) + or die "could not opendir \"$dir\": $!"; my @direntries = readdir $dh; closedir $dh; return @direntries; @@ -249,32 +165,18 @@ sub slurp_file return $contents; } -sub system_or_bail +sub append_to_file { - if (system_log(@_) != 0) - { - BAIL_OUT("system $_[0] failed: $?"); - } -} + my ($filename, $str) = @_; -sub system_log -{ - print("# Running: " . join(" ", @_) ."\n"); - return system(@_); + open my $fh, ">>", $filename or die "could not open \"$filename\": $!"; + print $fh $str; + close $fh; } -sub run_log -{ - print("# Running: " . join(" ", @{$_[0]}) ."\n"); - return run (@_); -} - - # # Test functions # - - sub command_ok { my ($cmd, $test_name) = @_; @@ -292,8 +194,8 @@ sub command_fails sub command_exit_is { my ($cmd, $expected, $test_name) = @_; - print("# Running: " . join(" ", @{$cmd}) ."\n"); - my $h = start $cmd; + print("# Running: " . join(" ", @{$cmd}) . "\n"); + my $h = IPC::Run::start $cmd; $h->finish(); # On Windows, the exit status of the process is returned directly as the @@ -303,8 +205,10 @@ sub command_exit_is # assuming the Unix convention, which will always return 0 on Windows as # long as the process was not terminated by an exception. To work around # that, use $h->full_result on Windows instead. - my $result = ($Config{osname} eq "MSWin32") ? - ($h->full_results)[0] : $h->result(0); + my $result = + ($Config{osname} eq "MSWin32") + ? ($h->full_results)[0] + : $h->result(0); is($result, $expected, $test_name); } @@ -313,7 +217,8 @@ sub program_help_ok my ($cmd) = @_; my ($stdout, $stderr); print("# Running: $cmd --help\n"); - my $result = run [ $cmd, '--help' ], '>', \$stdout, '2>', \$stderr; + my $result = IPC::Run::run [ $cmd, '--help' ], '>', \$stdout, '2>', + \$stderr; ok($result, "$cmd --help exit code 0"); isnt($stdout, '', "$cmd --help goes to stdout"); is($stderr, '', "$cmd --help nothing to stderr"); @@ -324,7 +229,8 @@ sub program_version_ok my ($cmd) = @_; my ($stdout, $stderr); print("# Running: $cmd --version\n"); - my $result = run [ $cmd, '--version' ], '>', \$stdout, '2>', \$stderr; + my $result = IPC::Run::run [ $cmd, '--version' ], '>', \$stdout, '2>', + \$stderr; ok($result, "$cmd --version exit code 0"); isnt($stdout, '', "$cmd --version goes to stdout"); is($stderr, '', "$cmd --version nothing to stderr"); @@ -335,8 +241,9 @@ sub program_options_handling_ok my ($cmd) = @_; my ($stdout, $stderr); print("# Running: $cmd --not-a-valid-option\n"); - my $result = run [ $cmd, '--not-a-valid-option' ], '>', \$stdout, '2>', - \$stderr; + my $result = IPC::Run::run [ $cmd, '--not-a-valid-option' ], '>', + \$stdout, + '2>', \$stderr; ok(!$result, "$cmd with invalid option nonzero exit code"); isnt($stderr, '', "$cmd with invalid option prints error message"); } @@ -346,20 +253,10 @@ sub command_like my ($cmd, $expected_stdout, $test_name) = @_; my ($stdout, $stderr); print("# Running: " . join(" ", @{$cmd}) . "\n"); - my $result = run $cmd, '>', \$stdout, '2>', \$stderr; + my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr; ok($result, "@$cmd exit code 0"); is($stderr, '', "@$cmd no stderr"); like($stdout, $expected_stdout, "$test_name: matches"); } -sub issues_sql_like -{ - my ($cmd, $expected_sql, $test_name) = @_; - truncate $test_server_logfile, 0; - my $result = run_log($cmd); - ok($result, "@$cmd exit code 0"); - my $log = slurp_file($test_server_logfile); - like($log, $expected_sql, "$test_name: SQL found in server log"); -} - 1; diff --git a/src/test/ssl/ServerSetup.pm b/src/test/ssl/ServerSetup.pm index a6c77b5c80..4e93184eb0 100644 --- a/src/test/ssl/ServerSetup.pm +++ b/src/test/ssl/ServerSetup.pm @@ -18,6 +18,7 @@ package ServerSetup; use strict; use warnings; +use PostgresNode; use TestLib; use File::Basename; use File::Copy; @@ -45,17 +46,19 @@ sub copy_files sub configure_test_server_for_ssl { - my $tempdir = $_[0]; + my $node = $_[0]; my $serverhost = $_[1]; + my $pgdata = $node->data_dir; + # Create test users and databases - psql 'postgres', "CREATE USER ssltestuser"; - psql 'postgres', "CREATE USER anotheruser"; - psql 'postgres', "CREATE DATABASE trustdb"; - psql 'postgres', "CREATE DATABASE certdb"; + $node->psql('postgres', "CREATE USER ssltestuser"); + $node->psql('postgres', "CREATE USER anotheruser"); + $node->psql('postgres', "CREATE DATABASE trustdb"); + $node->psql('postgres', "CREATE DATABASE certdb"); # enable logging etc. - open CONF, ">>$tempdir/pgdata/postgresql.conf"; + open CONF, ">>$pgdata/postgresql.conf"; print CONF "fsync=off\n"; print CONF "log_connections=on\n"; print CONF "log_hostname=on\n"; @@ -68,17 +71,17 @@ sub configure_test_server_for_ssl close CONF; # Copy all server certificates and keys, and client root cert, to the data dir - copy_files("ssl/server-*.crt", "$tempdir/pgdata"); - copy_files("ssl/server-*.key", "$tempdir/pgdata"); - chmod(0600, glob "$tempdir/pgdata/server-*.key") or die $!; - copy_files("ssl/root+client_ca.crt", "$tempdir/pgdata"); - copy_files("ssl/root+client.crl", "$tempdir/pgdata"); + copy_files("ssl/server-*.crt", $pgdata); + copy_files("ssl/server-*.key", $pgdata); + chmod(0600, glob "$pgdata/server-*.key") or die $!; + copy_files("ssl/root+client_ca.crt", $pgdata); + copy_files("ssl/root+client.crl", $pgdata); # Only accept SSL connections from localhost. Our tests don't depend on this # but seems best to keep it as narrow as possible for security reasons. # # When connecting to certdb, also check the client certificate. - open HBA, ">$tempdir/pgdata/pg_hba.conf"; + open HBA, ">$pgdata/pg_hba.conf"; print HBA "# TYPE DATABASE USER ADDRESS METHOD\n"; print HBA @@ -96,12 +99,13 @@ sub configure_test_server_for_ssl # the server so that the configuration takes effect. sub switch_server_cert { - my $tempdir = $_[0]; + my $node = $_[0]; my $certfile = $_[1]; + my $pgdata = $node->data_dir; diag "Restarting server with certfile \"$certfile\"..."; - open SSLCONF, ">$tempdir/pgdata/sslconfig.conf"; + open SSLCONF, ">$pgdata/sslconfig.conf"; print SSLCONF "ssl=on\n"; print SSLCONF "ssl_ca_file='root+client_ca.crt'\n"; print SSLCONF "ssl_cert_file='$certfile.crt'\n"; @@ -110,5 +114,5 @@ sub switch_server_cert close SSLCONF; # Stop and restart server to reload the new config. - restart_test_server(); + $node->restart; } diff --git a/src/test/ssl/t/001_ssltests.pl b/src/test/ssl/t/001_ssltests.pl index 0d6f339335..92f16e4a11 100644 --- a/src/test/ssl/t/001_ssltests.pl +++ b/src/test/ssl/t/001_ssltests.pl @@ -1,5 +1,7 @@ use strict; use warnings; +use PostgresNode; +use TestLib; use TestLib; use Test::More tests => 38; use ServerSetup; @@ -25,8 +27,6 @@ BEGIN # postgresql-ssl-regression.test. my $SERVERHOSTADDR = '127.0.0.1'; -my $tempdir = TestLib::tempdir; - # Define a couple of helper functions to test connecting to the server. my $common_connstr; @@ -74,10 +74,17 @@ chmod 0600, "ssl/client.key"; #### Part 0. Set up the server. -diag "setting up data directory in \"$tempdir\"..."; -start_test_server($tempdir); -configure_test_server_for_ssl($tempdir, $SERVERHOSTADDR); -switch_server_cert($tempdir, 'server-cn-only'); +diag "setting up data directory..."; +my $node = get_new_node(); +$node->init; + +# PGHOST is enforced here to set up the node, subsequent connections +# will use a dedicated connection string. +$ENV{PGHOST} = $node->host; +$ENV{PGPORT} = $node->port; +$node->start; +configure_test_server_for_ssl($node, $SERVERHOSTADDR); +switch_server_cert($node, 'server-cn-only'); ### Part 1. Run client-side tests. ### @@ -150,7 +157,7 @@ test_connect_ok("sslmode=verify-ca host=wronghost.test"); test_connect_fails("sslmode=verify-full host=wronghost.test"); # Test Subject Alternative Names. -switch_server_cert($tempdir, 'server-multiple-alt-names'); +switch_server_cert($node, 'server-multiple-alt-names'); diag "test hostname matching with X509 Subject Alternative Names"; $common_connstr = @@ -165,7 +172,7 @@ test_connect_fails("host=deep.subdomain.wildcard.pg-ssltest.test"); # Test certificate with a single Subject Alternative Name. (this gives a # slightly different error message, that's all) -switch_server_cert($tempdir, 'server-single-alt-name'); +switch_server_cert($node, 'server-single-alt-name'); diag "test hostname matching with a single X509 Subject Alternative Name"; $common_connstr = @@ -178,7 +185,7 @@ test_connect_fails("host=deep.subdomain.wildcard.pg-ssltest.test"); # Test server certificate with a CN and SANs. Per RFCs 2818 and 6125, the CN # should be ignored when the certificate has both. -switch_server_cert($tempdir, 'server-cn-and-alt-names'); +switch_server_cert($node, 'server-cn-and-alt-names'); diag "test certificate with both a CN and SANs"; $common_connstr = @@ -190,7 +197,7 @@ test_connect_fails("host=common-name.pg-ssltest.test"); # Finally, test a server certificate that has no CN or SANs. Of course, that's # not a very sensible certificate, but libpq should handle it gracefully. -switch_server_cert($tempdir, 'server-no-names'); +switch_server_cert($node, 'server-no-names'); $common_connstr = "user=ssltestuser dbname=trustdb sslcert=invalid sslrootcert=ssl/root+server_ca.crt hostaddr=$SERVERHOSTADDR"; @@ -199,7 +206,7 @@ test_connect_fails("sslmode=verify-full host=common-name.pg-ssltest.test"); # Test that the CRL works diag "Testing client-side CRL"; -switch_server_cert($tempdir, 'server-revoked'); +switch_server_cert($node, 'server-revoked'); $common_connstr = "user=ssltestuser dbname=trustdb sslcert=invalid hostaddr=$SERVERHOSTADDR host=common-name.pg-ssltest.test"; @@ -233,7 +240,3 @@ test_connect_fails( test_connect_fails( "user=ssltestuser sslcert=ssl/client-revoked.crt sslkey=ssl/client-revoked.key" ); - - -# All done! Save the log, before the temporary installation is deleted -copy("$tempdir/client-log", "./client-log");