aboutsummaryrefslogtreecommitdiff
path: root/src/test/perl/PostgresNode.pm
diff options
context:
space:
mode:
authorNoah Misch <noah@leadboat.com>2019-04-03 17:03:46 -0700
committerNoah Misch <noah@leadboat.com>2019-04-03 17:03:46 -0700
commit2f932f71d9f2963bbd201129d7b971c8f5f077fd (patch)
treea9647622166d61e3b7668eca618f37c47d864f5f /src/test/perl/PostgresNode.pm
parentaf052daec5aa282eab2d22fa3b24d6109a30c2f3 (diff)
downloadpostgresql-2f932f71d9f2963bbd201129d7b971c8f5f077fd.tar.gz
postgresql-2f932f71d9f2963bbd201129d7b971c8f5f077fd.zip
Consistently test for in-use shared memory.
postmaster startup scrutinizes any shared memory segment recorded in postmaster.pid, exiting if that segment matches the current data directory and has an attached process. When the postmaster.pid file was missing, a starting postmaster used weaker checks. Change to use the same checks in both scenarios. This increases the chance of a startup failure, in lieu of data corruption, if the DBA does "kill -9 `head -n1 postmaster.pid` && rm postmaster.pid && pg_ctl -w start". A postmaster will no longer recycle segments pertaining to other data directories. That's good for production, but it's bad for integration tests that crash a postmaster and immediately delete its data directory. Such a test now leaks a segment indefinitely. No "make check-world" test does that. win32_shmem.c already avoided all these problems. In 9.6 and later, enhance PostgresNode to facilitate testing. Back-patch to 9.4 (all supported versions). Reviewed by Daniel Gustafsson and Kyotaro HORIGUCHI. Discussion: https://postgr.es/m/20130911033341.GD225735@tornado.leadboat.com
Diffstat (limited to 'src/test/perl/PostgresNode.pm')
-rw-r--r--src/test/perl/PostgresNode.pm184
1 files changed, 147 insertions, 37 deletions
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 81deed98430..f6570639e00 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -104,7 +104,8 @@ our @EXPORT = qw(
get_new_node
);
-our ($test_localhost, $test_pghost, $last_port_assigned, @all_nodes, $died);
+our ($use_tcp, $test_localhost, $test_pghost, $last_host_assigned,
+ $last_port_assigned, @all_nodes, $died);
# Windows path to virtual file system root
@@ -118,13 +119,14 @@ if ($Config{osname} eq 'msys')
INIT
{
- # PGHOST is set once and for all through a single series of tests when
- # this module is loaded.
- $test_localhost = "127.0.0.1";
- $test_pghost =
- $TestLib::windows_os ? $test_localhost : TestLib::tempdir_short;
- $ENV{PGHOST} = $test_pghost;
- $ENV{PGDATABASE} = 'postgres';
+ # Set PGHOST for backward compatibility. This doesn't work for own_host
+ # nodes, so prefer to not rely on this when writing new tests.
+ $use_tcp = $TestLib::windows_os;
+ $test_localhost = "127.0.0.1";
+ $last_host_assigned = 1;
+ $test_pghost = $use_tcp ? $test_localhost : TestLib::tempdir_short;
+ $ENV{PGHOST} = $test_pghost;
+ $ENV{PGDATABASE} = 'postgres';
# Tracking of last port value assigned to accelerate free port lookup.
$last_port_assigned = int(rand() * 16384) + 49152;
@@ -155,7 +157,9 @@ sub new
_host => $pghost,
_basedir => "$TestLib::tmp_check/t_${testname}_${name}_data",
_name => $name,
- _logfile => "$TestLib::log_path/${testname}_${name}.log"
+ _logfile_generation => 0,
+ _logfile_base => "$TestLib::log_path/${testname}_${name}",
+ _logfile => "$TestLib::log_path/${testname}_${name}.log"
};
bless $self, $class;
@@ -473,8 +477,9 @@ sub init
print $conf "max_wal_senders = 0\n";
}
- if ($TestLib::windows_os)
+ if ($use_tcp)
{
+ print $conf "unix_socket_directories = ''\n";
print $conf "listen_addresses = '$host'\n";
}
else
@@ -536,12 +541,11 @@ sub backup
{
my ($self, $backup_name) = @_;
my $backup_path = $self->backup_dir . '/' . $backup_name;
- my $port = $self->port;
my $name = $self->name;
print "# Taking pg_basebackup $backup_name from node \"$name\"\n";
- TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-p', $port,
- '--no-sync');
+ TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-h',
+ $self->host, '-p', $self->port, '--no-sync');
print "# Backup finished\n";
return;
}
@@ -651,6 +655,7 @@ sub init_from_backup
{
my ($self, $root_node, $backup_name, %params) = @_;
my $backup_path = $root_node->backup_dir . '/' . $backup_name;
+ my $host = $self->host;
my $port = $self->port;
my $node_name = $self->name;
my $root_name = $root_node->name;
@@ -677,6 +682,15 @@ sub init_from_backup
qq(
port = $port
));
+ if ($use_tcp)
+ {
+ $self->append_conf('postgresql.conf', "listen_addresses = '$host'");
+ }
+ else
+ {
+ $self->append_conf('postgresql.conf',
+ "unix_socket_directories = '$host'");
+ }
$self->enable_streaming($root_node) if $params{has_streaming};
$self->enable_restoring($root_node) if $params{has_restoring};
return;
@@ -684,17 +698,45 @@ port = $port
=pod
-=item $node->start()
+=item $node->rotate_logfile()
+
+Switch to a new PostgreSQL log file. This does not alter any running
+PostgreSQL process. Subsequent method calls, including pg_ctl invocations,
+will use the new name. Return the new name.
+
+=cut
+
+sub rotate_logfile
+{
+ my ($self) = @_;
+ $self->{_logfile} = sprintf('%s_%d.log',
+ $self->{_logfile_base},
+ ++$self->{_logfile_generation});
+ return $self->{_logfile};
+}
+
+=pod
+
+=item $node->start(%params) => success_or_failure
Wrapper for pg_ctl start
Start the node and wait until it is ready to accept connections.
+=over
+
+=item fail_ok => 1
+
+By default, failure terminates the entire F<prove> invocation. If given,
+instead return a true or false value to indicate success or failure.
+
+=back
+
=cut
sub start
{
- my ($self) = @_;
+ my ($self, %params) = @_;
my $port = $self->port;
my $pgdata = $self->data_dir;
my $name = $self->name;
@@ -721,10 +763,34 @@ sub start
{
print "# pg_ctl start failed; logfile:\n";
print TestLib::slurp_file($self->logfile);
- BAIL_OUT("pg_ctl start failed");
+ BAIL_OUT("pg_ctl start failed") unless $params{fail_ok};
+ return 0;
}
$self->_update_pid(1);
+ return 1;
+}
+
+=pod
+
+=item $node->kill9()
+
+Send SIGKILL (signal 9) to the postmaster.
+
+Note: if the node is already known stopped, this does nothing.
+However, if we think it's running and it's not, it's important for
+this to fail. Otherwise, tests might fail to detect server crashes.
+
+=cut
+
+sub kill9
+{
+ my ($self) = @_;
+ my $name = $self->name;
+ return unless defined $self->{_pid};
+ print "### Killing node \"$name\" using signal 9\n";
+ kill(9, $self->{_pid}) or BAIL_OUT("kill(9, $self->{_pid}) failed");
+ $self->{_pid} = undef;
return;
}
@@ -965,7 +1031,7 @@ sub _update_pid
=pod
-=item PostgresNode->get_new_node(node_name)
+=item PostgresNode->get_new_node(node_name, %params)
Build a new object of class C<PostgresNode> (or of a subclass, if you have
one), assigning a free port number. Remembers the node, to prevent its port
@@ -974,6 +1040,22 @@ shut down when the test script exits.
You should generally use this instead of C<PostgresNode::new(...)>.
+=over
+
+=item port => [1,65535]
+
+By default, this function assigns a port number to each node. Specify this to
+force a particular port number. The caller is responsible for evaluating
+potential conflicts and privilege requirements.
+
+=item own_host => 1
+
+By default, all nodes use the same PGHOST value. If specified, generate a
+PGHOST specific to this node. This allows multiple nodes to use the same
+port.
+
+=back
+
For backwards compatibility, it is also exported as a standalone function,
which can only create objects of class C<PostgresNode>.
@@ -982,10 +1064,11 @@ which can only create objects of class C<PostgresNode>.
sub get_new_node
{
my $class = 'PostgresNode';
- $class = shift if 1 < scalar @_;
- my $name = shift;
- my $found = 0;
- my $port = $last_port_assigned;
+ $class = shift if scalar(@_) % 2 != 1;
+ my ($name, %params) = @_;
+ my $port_is_forced = defined $params{port};
+ my $found = $port_is_forced;
+ my $port = $port_is_forced ? $params{port} : $last_port_assigned;
while ($found == 0)
{
@@ -1002,13 +1085,15 @@ sub get_new_node
$found = 0 if ($node->port == $port);
}
- # Check to see if anything else is listening on this TCP port.
- # This is *necessary* on Windows, and seems like a good idea
- # on Unixen as well, even though we don't ask the postmaster
- # to open a TCP port on Unix.
+ # Check to see if anything else is listening on this TCP port. Accept
+ # only ports available for all possible listen_addresses values, so
+ # the caller can harness this port for the widest range of purposes.
+ # This is *necessary* on Windows, and seems like a good idea on Unixen
+ # as well, even though we don't ask the postmaster to open a TCP port
+ # on Unix.
if ($found == 1)
{
- my $iaddr = inet_aton($test_localhost);
+ my $iaddr = inet_aton('0.0.0.0');
my $paddr = sockaddr_in($port, $iaddr);
my $proto = getprotobyname("tcp");
@@ -1024,16 +1109,35 @@ sub get_new_node
}
}
- print "# Found free port $port\n";
+ print "# Found port $port\n";
+
+ # Select a host.
+ my $host = $test_pghost;
+ if ($params{own_host})
+ {
+ if ($use_tcp)
+ {
+ # This assumes $use_tcp platforms treat every address in
+ # 127.0.0.1/24, not just 127.0.0.1, as a usable loopback.
+ $last_host_assigned++;
+ $last_host_assigned > 254 and BAIL_OUT("too many own_host nodes");
+ $host = '127.0.0.' . $last_host_assigned;
+ }
+ else
+ {
+ $host = "$test_pghost/$name"; # Assume $name =~ /^[-_a-zA-Z0-9]+$/
+ mkdir $host;
+ }
+ }
# Lock port number found by creating a new node
- my $node = $class->new($name, $test_pghost, $port);
+ my $node = $class->new($name, $host, $port);
# Add node to list of nodes
push(@all_nodes, $node);
# And update port for next time
- $last_port_assigned = $port;
+ $port_is_forced or $last_port_assigned = $port;
return $node;
}
@@ -1424,9 +1528,8 @@ $stderr);
=item $node->command_ok(...)
-Runs a shell command like TestLib::command_ok, but with PGPORT
-set so that the command will default to connecting to this
-PostgresNode.
+Runs a shell command like TestLib::command_ok, but with PGHOST and PGPORT set
+so that the command will default to connecting to this PostgresNode.
=cut
@@ -1436,6 +1539,7 @@ sub command_ok
my $self = shift;
+ local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port;
TestLib::command_ok(@_);
@@ -1446,7 +1550,7 @@ sub command_ok
=item $node->command_fails(...)
-TestLib::command_fails with our PGPORT. See command_ok(...)
+TestLib::command_fails with our connection parameters. See command_ok(...)
=cut
@@ -1456,6 +1560,7 @@ sub command_fails
my $self = shift;
+ local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port;
TestLib::command_fails(@_);
@@ -1466,7 +1571,7 @@ sub command_fails
=item $node->command_like(...)
-TestLib::command_like with our PGPORT. See command_ok(...)
+TestLib::command_like with our connection parameters. See command_ok(...)
=cut
@@ -1476,6 +1581,7 @@ sub command_like
my $self = shift;
+ local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port;
TestLib::command_like(@_);
@@ -1486,7 +1592,8 @@ sub command_like
=item $node->command_checks_all(...)
-TestLib::command_checks_all with our PGPORT. See command_ok(...)
+TestLib::command_checks_all with our connection parameters. See
+command_ok(...)
=cut
@@ -1496,6 +1603,7 @@ sub command_checks_all
my $self = shift;
+ local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port;
TestLib::command_checks_all(@_);
@@ -1520,6 +1628,7 @@ sub issues_sql_like
my ($self, $cmd, $expected_sql, $test_name) = @_;
+ local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port;
truncate $self->logfile, 0;
@@ -1534,8 +1643,8 @@ sub issues_sql_like
=item $node->run_log(...)
-Runs a shell command like TestLib::run_log, but with PGPORT set so
-that the command will default to connecting to this PostgresNode.
+Runs a shell command like TestLib::run_log, but with connection parameters set
+so that the command will default to connecting to this PostgresNode.
=cut
@@ -1543,6 +1652,7 @@ sub run_log
{
my $self = shift;
+ local $ENV{PGHOST} = $self->host;
local $ENV{PGPORT} = $self->port;
TestLib::run_log(@_);