From 077e447995b3b8bb4c0594a6616e1350acd9d48b Mon Sep 17 00:00:00 2001
From: Jehan-Guillaume de Rorthais <jgdr@dalibo.com>
Date: Mon, 12 Apr 2021 14:45:17 +0200
Subject: [PATCH] Draft: Makes PostgresNode multi-version aware

---
 src/test/perl/PostgresNode.pm    | 708 ++++++++++++++++++++++++++++---
 src/test/perl/PostgresVersion.pm |  65 +++
 2 files changed, 704 insertions(+), 69 deletions(-)
 create mode 100644 src/test/perl/PostgresVersion.pm

diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index e26b2b3f30..d7a9e54efd 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -102,6 +102,7 @@ use Test::More;
 use TestLib ();
 use Time::HiRes qw(usleep);
 use Scalar::Util qw(blessed);
+use PostgresVersion;
 
 our @EXPORT = qw(
   get_new_node
@@ -376,9 +377,42 @@ sub dump_info
 	return;
 }
 
+# Internal routine to allows streaming replication on a primary node.
+sub allows_streaming
+{
+	my ($self, $allows_streaming) = @_;
+	my $pgdata = $self->data_dir;
+	my $wal_level;
+
+	if ($allows_streaming eq "logical")
+	{
+		$wal_level = "logical";
+	}
+	else
+	{
+		$wal_level = "replica";
+	}
+
+	$self->append_conf( 'postgresql.conf', qq{
+		wal_level = $wal_level
+		max_wal_senders = 10
+		max_replication_slots = 10
+		wal_log_hints = on
+		hot_standby = on
+		# conservative settings to ensure we can run multiple postmasters:
+		shared_buffers = 1MB
+		max_connections = 10
+		# limit disk space consumption, too:
+		max_wal_size = 128MB
+	});
 
-# Internal method to set up trusted pg_hba.conf for replication.  Not
-# documented because you shouldn't use it, it's called automatically if needed.
+	$self->set_replication_conf;
+
+	return;
+}
+
+# Internal to set up trusted pg_hba.conf for replication.  Not documented
+# because you shouldn't use it, it's called automatically if needed.
 sub set_replication_conf
 {
 	my ($self) = @_;
@@ -398,6 +432,15 @@ sub set_replication_conf
 	return;
 }
 
+# Internal methods to track capabilities depending on the PostgreSQL major
+# version used
+
+sub can_slots               { return 1 }
+sub can_log_hints           { return 1 }
+sub can_restart_after_crash { return 1 }
+sub can_skip_init_fsync     { return 1 }
+
+
 =pod
 
 =item $node->init(...)
@@ -429,6 +472,7 @@ sub init
 	my $port   = $self->port;
 	my $pgdata = $self->data_dir;
 	my $host   = $self->host;
+	my @cmd;
 
 	local %ENV = $self->_get_env();
 
@@ -438,19 +482,25 @@ sub init
 	mkdir $self->backup_dir;
 	mkdir $self->archive_dir;
 
-	TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
-		@{ $params{extra} });
+	@cmd = ( 'initdb', '-D', $pgdata, '-A', 'trust' );
+	push @cmd, '-N' if $self->can_skip_init_fsync;
+	push @cmd, @{ $params{extra} } if defined $params{extra};
+
+	TestLib::system_or_bail(@cmd);
 	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
 		@{ $params{auth_extra} });
 
 	open my $conf, '>>', "$pgdata/postgresql.conf";
 	print $conf "\n# Added by PostgresNode.pm\n";
 	print $conf "fsync = off\n";
-	print $conf "restart_after_crash = off\n";
+	print $conf "restart_after_crash = off\n" if $self->can_restart_after_crash;
 	print $conf "log_line_prefix = '%m [%p] %q%a '\n";
 	print $conf "log_statement = all\n";
-	print $conf "log_replication_commands = on\n";
-	print $conf "wal_retrieve_retry_interval = '500ms'\n";
+	if ($self->version >= '9.5.0')
+	{
+		print $conf "log_replication_commands = on\n";
+		print $conf "wal_retrieve_retry_interval = '500ms'\n";
+	}
 
 	# If a setting tends to affect whether tests pass or fail, print it after
 	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
@@ -463,31 +513,8 @@ sub init
 	# concurrently must not share a stats_temp_directory.
 	print $conf "stats_temp_directory = 'pg_stat_tmp'\n";
 
-	if ($params{allows_streaming})
-	{
-		if ($params{allows_streaming} eq "logical")
-		{
-			print $conf "wal_level = logical\n";
-		}
-		else
-		{
-			print $conf "wal_level = replica\n";
-		}
-		print $conf "max_wal_senders = 10\n";
-		print $conf "max_replication_slots = 10\n";
-		print $conf "wal_log_hints = on\n";
-		print $conf "hot_standby = on\n";
-		# conservative settings to ensure we can run multiple postmasters:
-		print $conf "shared_buffers = 1MB\n";
-		print $conf "max_connections = 10\n";
-		# limit disk space consumption, too:
-		print $conf "max_wal_size = 128MB\n";
-	}
-	else
-	{
-		print $conf "wal_level = minimal\n";
-		print $conf "max_wal_senders = 0\n";
-	}
+	print $conf "wal_level = minimal\n";
+	print $conf "max_wal_senders = 0\n";
 
 	print $conf "port = $port\n";
 	if ($use_tcp)
@@ -505,8 +532,8 @@ sub init
 	chmod($self->group_access ? 0640 : 0600, "$pgdata/postgresql.conf")
 	  or die("unable to set permissions for $pgdata/postgresql.conf");
 
-	$self->set_replication_conf if $params{allows_streaming};
-	$self->enable_archiving     if $params{has_archiving};
+	$self->allows_streaming($params{allows_streaming}) if $params{allows_streaming};
+	$self->enable_archiving                            if $params{has_archiving};
 	return;
 }
 
@@ -986,8 +1013,8 @@ primary_conninfo='$root_connstr'
 sub enable_restoring
 {
 	my ($self, $root_node, $standby) = @_;
-	my $path = TestLib::perl2host($root_node->archive_dir);
-	my $name = $self->name;
+	my $path     = TestLib::perl2host($root_node->archive_dir);
+	my $name     = $self->name;
 
 	print "### Enabling WAL restore for node \"$name\"\n";
 
@@ -1038,7 +1065,7 @@ sub set_recovery_mode
 
 =item $node->set_standby_mode()
 
-Place standby.signal file.
+Place standby.signal file or set standby_mode = on.
 
 =cut
 
@@ -1193,6 +1220,13 @@ sub get_new_node
 		$node->{_install_path} = $params{install_path};
 	}
 
+	# validate install path and set version
+	# FIXME: this should probably go to the constructor as various
+	#        methods need to be able to read the cluster version
+	$node->_read_pg_config;
+
+	bless $node, $node->version->get_class;
+
 	# Add node to list of nodes
 	push(@all_nodes, $node);
 
@@ -1270,6 +1304,48 @@ sub _get_env
 	}
 	return (%inst_env);
 }
+ 
+# Private routine to run the pg_config binary found in our environment (or in
+# our install_path, if we have one), and collect all fields that matter to us.
+#
+sub _read_pg_config
+{
+	my ($self, $inst) = @_;
+	my $pg_config = "pg_config";
+
+	if (defined $inst)
+	{
+		# If the _install_path is invalid, our PATH variables might find an
+		# unrelated pg_config executable elsewhere.  Sanity check the
+		# directory.
+		BAIL_OUT("directory not found: $inst")
+			unless -d $inst;
+
+		# If the directory exists but is not the root of a postgresql
+		# installation, or if the user configured using
+		# --bindir=$SOMEWHERE_ELSE, we're not going to find pg_config, so
+		# complain about that, too.
+		$pg_config = "$inst/bin/pg_config";
+		BAIL_OUT("pg_config not found: $pg_config")
+			unless -e $pg_config;
+		BAIL_OUT("pg_config not executable: $pg_config")
+			unless -x $pg_config;
+
+		# Leave $pg_config install_path qualified, to be sure we get the right
+		# version information, below, or die trying
+	}
+
+	local %ENV = $self->_get_env();
+
+	# We only want the version field
+	open my $fh, "-|", $pg_config, "--version"
+		or
+		BAIL_OUT("$pg_config failed: $!");
+	my $version_line = <$fh>;
+	close $fh or die;
+
+	$self->{_pg_version} = PostgresVersion->new($version_line);
+}
 
 =pod
 
@@ -1382,6 +1458,11 @@ END
 	$? = $exit_code;
 }
 
+sub version {
+	my $self = shift;
+	return $self->{_pg_version};
+}
+
 =pod
 
 =item $node->teardown_node()
@@ -1920,17 +2001,7 @@ sub connect_ok
 	if (@log_like or @log_unlike)
 	{
 		# Don't let previous log entries match for this connection.
-		# On Windows, the truncation would not work, so rotate the log
-		# file before restarting the server afresh.
-		if ($TestLib::windows_os)
-		{
-			$self->rotate_logfile;
-			$self->restart;
-		}
-		else
-		{
-			truncate $self->logfile, 0;
-		}
+		truncate $self->logfile, 0;
 	}
 
 	# Never prompt for a password, any callers of this routine should
@@ -2004,17 +2075,7 @@ sub connect_fails
 	if (@log_like or @log_unlike)
 	{
 		# Don't let previous log entries match for this connection.
-		# On Windows, the truncation would not work, so rotate the log
-		# file before restarting the server afresh.
-		if ($TestLib::windows_os)
-		{
-			$self->rotate_logfile;
-			$self->restart;
-		}
-		else
-		{
-			truncate $self->logfile, 0;
-		}
+		truncate $self->logfile, 0;
 	}
 
 	# Never prompt for a password, any callers of this routine should
@@ -2304,8 +2365,12 @@ This is not a test. It die()s on failure.
 sub wait_for_catchup
 {
 	my ($self, $standby_name, $mode, $target_lsn) = @_;
+	my $lsn_expr;
+	my $query;
+	my %valid_modes;
+
 	$mode = defined($mode) ? $mode : 'replay';
-	my %valid_modes =
+	%valid_modes =
 	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
 	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
 	  . join(', ', keys(%valid_modes))
@@ -2316,7 +2381,7 @@ sub wait_for_catchup
 	{
 		$standby_name = $standby_name->name;
 	}
-	my $lsn_expr;
+	
 	if (defined($target_lsn))
 	{
 		$lsn_expr = "'$target_lsn'";
@@ -2325,16 +2390,19 @@ sub wait_for_catchup
 	{
 		$lsn_expr = 'pg_current_wal_lsn()';
 	}
-	print "Waiting for replication conn "
-	  . $standby_name . "'s "
-	  . $mode
-	  . "_lsn to pass "
-	  . $lsn_expr . " on "
-	  . $self->name . "\n";
-	my $query =
-	  qq[SELECT $lsn_expr <= ${mode}_lsn AND state = 'streaming' FROM pg_catalog.pg_stat_replication WHERE application_name = '$standby_name';];
+
+	print "Waiting for replication conn ${standby_name}'s ${mode}_lsn "
+		. "to pass $lsn_expr on " . $self->name . "\n";
+
+	$query = qq[
+		SELECT $lsn_expr <= ${mode}_lsn AND state = 'streaming'
+		FROM pg_catalog.pg_stat_replication
+		WHERE application_name = '$standby_name'
+	];
+
 	$self->poll_query_until('postgres', $query)
 	  or croak "timed out waiting for catchup";
+
 	print "done\n";
 	return;
 }
@@ -2556,4 +2624,506 @@ sub pg_recvlogical_upto
 
 =cut
 
+package PostgresNode13;
+
+use strict;
+use warnings;
+use parent 'PostgresNode';
+
+
+package PostgresNode12;
+
+use strict;
+use warnings;
+use parent 'PostgresNode';
+
+package PostgresNode11;
+
+use strict;
+use warnings;
+use Carp;
+use parent 'PostgresNode';
+
+# superuser_reserved_connections + max_wal_senders must be less
+# than max_connections
+sub allows_streaming
+{
+	my ($self, $allows_streaming) = @_;
+	my $pgdata = $self->data_dir;
+	my $wal_level;
+
+	if ($allows_streaming eq "logical")
+	{
+		$wal_level = "logical";
+	}
+	else
+	{
+		$wal_level = "replica";
+	}
+
+	$self->append_conf( 'postgresql.conf', qq{
+		wal_level = $wal_level
+		max_wal_senders = 5
+		max_replication_slots = 10
+		wal_log_hints = on
+		hot_standby = on
+		# conservative settings to ensure we can run multiple postmasters:
+		shared_buffers = 1MB
+		max_connections = 10
+		# limit disk space consumption, too:
+		max_wal_size = 128MB
+	});
+
+	$self->set_replication_conf;
+
+	return;
+}
+
+# standby mode set as GUC in recovery.conf file
+sub set_standby_mode
+{
+	my ($self) = @_;
+
+	$self->append_conf( 'recovery.conf', 'standby_mode = on');
+
+	return;
+}
+
+# recovery mode when recovery.conf file exists with standby_mode = off
+sub set_recovery_mode
+{
+	my ($self) = @_;
+
+	$self->append_conf( 'recovery.conf', 'standby_mode = off');
+
+	return;
+}
+
+# Internal routine to enable archive recovery command on a standby node
+sub enable_restoring
+{
+	my ($self, $root_node, $standby) = @_;
+	my $path     = TestLib::perl2host($root_node->archive_dir);
+	my $name     = $self->name;
+
+	print "### Enabling WAL restore for node \"$name\"\n";
+
+	# FIXME: use a dedicated command to build the restore command?
+	# On Windows, the path specified in the restore command needs to use
+	# double back-slashes to work properly and to be able to detect properly
+	# the file targeted by the copy command, so the directory value used
+	# in this routine, using only one back-slash, need to be properly changed
+	# first. Paths also need to be double-quoted to prevent failures where
+	# the path contains spaces.
+	$path =~ s{\\}{\\\\}g if ($TestLib::windows_os);
+	my $copy_command =
+	  $TestLib::windows_os
+	  ? qq{copy "$path\\\\%f" "%p"}
+	  : qq{cp "$path/%f" "%p"};
+
+	$self->append_conf(
+		'recovery.conf', qq(
+restore_command = '$copy_command'
+));
+	if ($standby)
+	{
+		$self->set_standby_mode();
+	}
+	else
+	{
+		$self->set_recovery_mode();
+	}
+	return;
+}
+
+# Internal routine to enable streaming replication on a standby node.
+# * up to version 11, replication setting is in recovery.conf.
+# * up to version 11, we need to explicitly set application_name as cluster_name
+#   is not used as a fallback.
+sub enable_streaming
+{
+	my ($self, $root_node) = @_;
+	my $root_connstr = $root_node->connstr;
+	my $name         = $self->name;
+
+	print "### Enabling streaming replication for node \"$name\"\n";
+	$self->append_conf(
+		'recovery.conf', qq(
+primary_conninfo='$root_connstr application_name=$name'
+));
+	$self->set_standby_mode();
+	return;
+}
+
+package PostgresNode10;
+
+use strict;
+use warnings;
+use parent -norequire, 'PostgresNode11';
+
+package PostgresNode96;
+
+use strict;
+use warnings;
+use Carp;
+use Scalar::Util qw(blessed);
+use parent -norequire, 'PostgresNode11';
+
+# pg_ctl doesn't wait by default for full-start in 9.6 and before
+sub start
+{
+	my ($self, %params) = @_;
+	my $port   = $self->port;
+	my $pgdata = $self->data_dir;
+	my $name   = $self->name;
+	my $ret;
+
+	BAIL_OUT("node \"$name\" is already running") if defined $self->{_pid};
+
+	print("### Starting node \"$name\"\n");
+
+	# Temporarily unset PGAPPNAME so that the server doesn't
+	# inherit it.  Otherwise this could affect libpqwalreceiver
+	# connections in confusing ways.
+	local %ENV = $self->_get_env(PGAPPNAME => undef);
+
+	# Note: We set the cluster_name here, not in postgresql.conf (in
+	# sub init) so that it does not get copied to standbys.
+	$ret = TestLib::system_log('pg_ctl', '-D', $self->data_dir, '-l',
+		$self->logfile, '-o', "--cluster-name=$name", '-w', 'start');
+
+	if ($ret != 0)
+	{
+		print "# pg_ctl start failed; logfile:\n";
+		print TestLib::slurp_file($self->logfile);
+		BAIL_OUT("pg_ctl start failed") unless $params{fail_ok};
+		return 0;
+	}
+
+	$self->_update_pid(1);
+	return 1;
+}
+
+# '--no-sync' doesn't exist in 9.6 and before
+sub backup
+{
+	my ($self, $backup_name, %params) = @_;
+	my $backup_path = $self->backup_dir . '/' . $backup_name;
+	my $name        = $self->name;
+
+	local %ENV = $self->_get_env();
+
+	print "# Taking pg_basebackup $backup_name from node \"$name\"\n";
+	TestLib::system_or_bail(
+		'pg_basebackup', '-D', $backup_path, '-h',
+		$self->host,     '-p', $self->port,  '--checkpoint',
+		'fast', @{ $params{backup_options} });
+	print "# Backup finished\n";
+	return;
+}
+
+# need to explicitly allow replication in pg_hba.conf for 9.6 and before
+sub set_replication_conf
+{
+	my ($self) = @_;
+	my $pgdata = $self->data_dir;
+
+	$self->host eq $test_pghost
+	  or croak "set_replication_conf only works with the default host";
+
+	open my $hba, '>>', "$pgdata/pg_hba.conf";
+	print $hba "\n# Allow replication (set up by PostgresNode.pm)\n";
+	if ($TestLib::windows_os && !$TestLib::use_unix_sockets)
+	{
+		print $hba
+		  "host replication all $test_localhost/32 sspi include_realm=1 map=regress\n";
+	}
+	else {
+		print $hba "local replication all trust\n";
+	}
+
+	close $hba;
+	return;
+}
+
+# various function have been renamed after 9.6
+sub lsn
+{
+	my ($self, $mode) = @_;
+	my %modes = (
+		'insert'  => 'pg_current_xlog_insert_location()',
+		'flush'   => 'pg_current_xlog_flush_location()',
+		'write'   => 'pg_current_xlog_location()',
+		'receive' => 'pg_last_xlog_receive_location()',
+		'replay'  => 'pg_last_xlog_replay_location()');
+
+	$mode = '<undef>'  unless defined $mode;
+	croak "unknown mode for 'lsn': '$mode', valid modes are "
+	  . join(', ', keys %modes)
+	  if !defined($modes{$mode});
+
+	my $result = $self->safe_psql('postgres', "SELECT $modes{$mode}");
+	chomp($result);
+	if ($result eq '')
+	{
+		return;
+	}
+	else
+	{
+		return $result;
+	}
+}
+
+# field renamed after 9.6
+sub wait_for_catchup
+{
+	my ($self, $standby_name, $mode, $target_lsn) = @_;
+	my $lsn_expr;
+	my $query;
+	my %valid_modes;
+
+	$mode = defined($mode) ? $mode : 'replay';
+	%valid_modes =
+	  ('sent' => 1, 'write' => 1, 'flush' => 1, 'replay' => 1);
+	croak "unknown mode $mode for 'wait_for_catchup', valid modes are "
+	  . join(', ', keys(%valid_modes))
+	  unless exists($valid_modes{$mode});
+
+	# Allow passing of a PostgresNode instance as shorthand
+	if (blessed($standby_name) && $standby_name->isa("PostgresNode"))
+	{
+		$standby_name = $standby_name->name;
+	}
+
+	if (defined($target_lsn))
+	{
+		$lsn_expr = "'$target_lsn'";
+	}
+	else
+	{
+		$lsn_expr = 'pg_current_wal_lsn()';
+	}
+
+	print "Waiting for replication conn ${standby_name}'s ${mode}_location "
+        . "to pass $lsn_expr on " . $self->name . "\n";
+
+	$query = qq[
+		SELECT $lsn_expr <= ${mode}_location AND state = 'streaming'
+		FROM pg_catalog.pg_stat_replication
+		WHERE application_name = '$standby_name'
+	];
+
+	$self->poll_query_until('postgres', $query)
+	  or croak "timed out waiting for catchup";
+
+	print "done\n";
+	return;
+}
+
+
+package PostgresNode95;
+
+use strict;
+use warnings;
+use parent -norequire, 'PostgresNode96';
+
+# "wal_level = replicat" does not exist in 9.5.
+sub allows_streaming {
+	my $self = shift;
+
+	$self->SUPER::allows_streaming(@_);
+
+	$self->append_conf('postgresql.conf', 'wal_level = hot_standby');
+
+	return;
+}
+
+
+package PostgresNode94;
+
+use strict;
+use warnings;
+use parent -norequire, 'PostgresNode95';
+use Time::HiRes qw(usleep);
+
+# "max_wal_size" does not exists in 9.4. No need to lower "checkpoint_segments"
+# as it is already low.
+# Moreover, we can not just overload wal_level as 9.4 blocks on first bad
+# GUC value.
+sub allows_streaming
+{
+	my ($self, $allows_streaming) = @_;
+	my $pgdata = $self->data_dir;
+	my $wal_level;
+
+	if ($allows_streaming eq "logical")
+	{
+		$wal_level = "logical";
+	}
+	else
+	{
+		$wal_level = "hot_standby";
+	}
+
+	$self->append_conf( 'postgresql.conf', qq{
+		wal_level = $wal_level
+		max_wal_senders = 5
+		max_replication_slots = 10
+		wal_log_hints = on
+		hot_standby = on
+		# conservative settings to ensure we can run multiple postmasters:
+		shared_buffers = 1MB
+		max_connections = 10
+	});
+
+	$self->set_replication_conf;
+
+	return;
+}
+
+# * "cluster-name" not supported in 9.4 and before
+# * pg_ctl -w does not wait for postmaster.pid to be ready
+#   see: f13ea95f9e473
+sub start
+{
+	my ($self, %params) = @_;
+	my $port            = $self->port;
+	my $pgdata          = $self->data_dir;
+	my $name            = $self->name;
+	my $pidfile         = $self->data_dir . "/postmaster.pid";
+    my $max_attempts    = 300; # 30s
+	my $ret;
+
+	BAIL_OUT("node \"$name\" is already running") if defined $self->{_pid};
+
+	print("### Starting node \"$name\"\n");
+
+	# Temporarily unset PGAPPNAME so that the server doesn't
+	# inherit it.  Otherwise this could affect libpqwalreceiver
+	# connections in confusing ways.
+	local %ENV = $self->_get_env(PGAPPNAME => undef);
+
+	# Note: We set the cluster_name here, not in postgresql.conf (in
+	# sub init) so that it does not get copied to standbys.
+	$ret = TestLib::system_log('pg_ctl', '-D', $self->data_dir, '-l',
+		$self->logfile, '-w', 'start');
+
+	while ($max_attempts and not -f $pidfile)
+	{
+		$max_attempts--;
+		# Wait 0.1 second before retrying.
+		usleep(100_000);
+	}
+
+	if ($ret != 0)
+	{
+		print "# pg_ctl start failed; logfile:\n";
+		print TestLib::slurp_file($self->logfile);
+		BAIL_OUT("pg_ctl start failed") unless $params{fail_ok};
+		return 0;
+	}
+
+	if (not -f $pidfile) {
+		print "# timeout while waiting for postmaster.pid; logfile:\n";
+		print TestLib::slurp_file($self->logfile);
+		BAIL_OUT("pg_ctl start failed") unless $params{fail_ok};
+		return 0;
+	}
+
+	$self->_update_pid(1);
+	return 1;
+}
+
+
+package PostgresNode93;
+
+use strict;
+use warnings;
+use parent -norequire, 'PostgresNode94';
+
+
+# "wal_level = logical" doesn't exists
+sub allows_streaming
+{
+	my ($self, $allows_streaming) = @_;
+	my $pgdata = $self->data_dir;
+
+	$self->append_conf( 'postgresql.conf', qq{
+		wal_level = hot_standby
+		max_wal_senders = 5
+		hot_standby = on
+		# conservative settings to ensure we can run multiple postmasters:
+		shared_buffers = 1MB
+		max_connections = 10
+	});
+
+	$self->set_replication_conf;
+
+	return;
+}
+
+sub can_slots     { return 0 }
+sub can_log_hints { return 0 }
+
+package PostgresNode92;
+
+use strict;
+use warnings;
+use parent -norequire, 'PostgresNode93';
+use TestLib ();
+
+sub can_skip_init_fsync { return 0 }
+
+sub init
+{
+	my $self = shift;
+
+	$self->SUPER::init(@_);
+	$self->rename_unix_socket_dir;
+
+	return;
+}
+
+sub init_from_backup
+{
+	my $self = shift;
+
+	$self->SUPER::init_from_backup(@_);
+	$self->rename_unix_socket_dir;
+
+	return;
+}
+
+sub rename_unix_socket_dir
+{
+	my $self = shift;
+	my $file = $self->data_dir ."/postgresql.conf";
+	my $conf;
+
+	$conf = TestLib::slurp_file($file);
+
+	$conf =~ s/unix_socket_directories/unix_socket_directory/g;
+
+	open my $fd, '>', $file;
+	print $fd $conf;
+	close $fd;
+
+	return;
+}
+
+package PostgresNode91;
+
+use strict;
+use warnings;
+use parent -norequire, 'PostgresNode92';
+
+
+package PostgresNode90;
+
+use strict;
+use warnings;
+use parent -norequire, 'PostgresNode92';
+
+sub can_restart_after_crash { return 0 }
+
 1;
diff --git a/src/test/perl/PostgresVersion.pm b/src/test/perl/PostgresVersion.pm
new file mode 100644
index 0000000000..ca8c0ffe6f
--- /dev/null
+++ b/src/test/perl/PostgresVersion.pm
@@ -0,0 +1,65 @@
+#!/usr/bin/perl
+
+package PostgresVersion;
+
+use strict;
+use warnings;
+use Carp;
+
+use overload	'""' => sub { $_[0]->{'version'} },
+				#'>' => \&vers_gt,
+				'<=>' => \&vers_cmp;
+
+sub new {
+	my $class = shift;
+	my $ver   = shift;
+	my $errmsg = "could not parse pg_config --version output: $ver";
+	my $self;
+
+	$self->{'vernum'} = 0;
+	$self->{'version'} = 0;
+
+	$ver =~ /(?:PostgreSQL )?(\d+)\.(\d+)(?:\.(\d+))?/;
+
+	croak $errmsg unless defined $1 and defined $2;
+
+	$self->{'version'} = $1;
+	$self->{'vernum'} += 10000 * $1;
+	$self->{'version'} .= ".$2";
+
+	if ($self->{'vernum'} >= 100000) {
+		croak $errmsg if defined $3;
+		$self->{'vernum'} += $2;
+		$self->{'class'} = "$1";
+	}
+	else {
+		#croak $errmsg unless defined $3;
+		my $minor = defined $3 ? $3 : 0;
+		$self->{'vernum'} += 100 * $2 + $minor;
+		$self->{'version'} .= ".$minor";
+		$self->{'class'} = "$1$2"
+	}
+
+	return bless $self, $class
+}
+
+sub get_class {
+	my $self = shift;
+	return "PostgresNode$self->{'class'}";
+}
+
+sub vers_cmp {
+	my ($a, $b, $swapped) = @_;
+	my $res = 1;
+
+	$b = PostgresVersion->new($b) unless ref($b) eq 'PostgresVersion';
+
+	return 0 if $a->{'vernum'} == $b->{'vernum'};
+	$res = -1 if $a->{'vernum'} < $b->{'vernum'};
+
+	$res *= -1 if $swapped;
+
+	return $res;
+}
+
+1
-- 
2.20.1

