From 4f2930b883e8180523da1140b6c54fdf2d502be6 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Wed, 31 Mar 2021 19:14:31 -0700
Subject: [PATCH v1 1/2] Extending PostgresNode cross-version functionality

Extending the recently introduced functionality that allows a
PostgresNode to be formed using an older installation.  First,
adding functions that allow nodes to be compared, such as

  if ($some_node->newer_than_version($some_other_node)) { ... }

  if ($node_node->at_least_version("12.2")) { ... }

Fixing PostgresNode::init() to work with versions older than 9.3.

Sanity check the install_path argument.

Fixing PostgresNode::start() to work with versions old than 10.
---
 src/test/perl/PostgresNode.pm | 198 ++++++++++++++++++++++++++++++++--
 1 file changed, 188 insertions(+), 10 deletions(-)

diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index d6e10544bb..c64380d608 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -438,7 +438,8 @@ sub init
 	mkdir $self->backup_dir;
 	mkdir $self->archive_dir;
 
-	TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust', '-N',
+	TestLib::system_or_bail('initdb', '-D', $pgdata, '-A', 'trust',
+		$self->at_least_version("9.3") ? '-N' : (),
 		@{ $params{extra} });
 	TestLib::system_or_bail($ENV{PG_REGRESS}, '--config-auth', $pgdata,
 		@{ $params{auth_extra} });
@@ -446,11 +447,14 @@ sub init
 	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->at_least_version("9.1");
 	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";
+	print $conf "log_replication_commands = on\n"
+		if $self->at_least_version("9.5");
+	print $conf "wal_retrieve_retry_interval = '500ms'\n"
+		if $self->at_least_version("9.5");
 
 	# If a setting tends to affect whether tests pass or fail, print it after
 	# TEMP_CONFIG.  Otherwise, print it before TEMP_CONFIG, thereby permitting
@@ -492,12 +496,14 @@ sub init
 	print $conf "port = $port\n";
 	if ($use_tcp)
 	{
-		print $conf "unix_socket_directories = ''\n";
+		print $conf "unix_socket_directories = ''\n"
+			if $self->at_least_version("9.3");
 		print $conf "listen_addresses = '$host'\n";
 	}
 	else
 	{
-		print $conf "unix_socket_directories = '$host'\n";
+		print $conf "unix_socket_directories = '$host'\n"
+			if $self->at_least_version("9.3");
 		print $conf "listen_addresses = ''\n";
 	}
 	close $conf;
@@ -732,7 +738,8 @@ port = $port
 	else
 	{
 		$self->append_conf('postgresql.conf',
-			"unix_socket_directories = '$host'");
+			"unix_socket_directories = '$host'")
+			if $self->at_least_version("9.3");
 	}
 	$self->enable_streaming($root_node) if $params{has_streaming};
 	$self->enable_restoring($root_node, $params{standby})
@@ -797,8 +804,12 @@ sub start
 
 	# 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", 'start');
+	$ret = TestLib::system_log('pg_ctl',
+		$self->older_than_version('10') ? '-w' : (),
+		'-D', $self->data_dir, '-l',
+		$self->logfile,
+		$self->at_least_version('9.5') ? ('-o', "--cluster-name=$name") : (),
+		'start');
 
 	if ($ret != 0)
 	{
@@ -911,7 +922,9 @@ sub restart
 
 	print "### Restarting node \"$name\"\n";
 
-	TestLib::system_or_bail('pg_ctl', '-D', $pgdata, '-l', $logfile,
+	TestLib::system_or_bail('pg_ctl',
+		$self->older_than_version('10') ? '-w' : (),
+		'-D', $pgdata, '-l', $logfile,
 		'restart');
 
 	$self->_update_pid(1);
@@ -1196,9 +1209,174 @@ sub get_new_node
 	# Add node to list of nodes
 	push(@all_nodes, $node);
 
+	# Get information about the node
+	$node->_read_pg_config;
+
 	return $node;
 }
 
+# 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) = @_;
+	my $inst = $self->{_install_path};
+	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} = _pg_version_array($version_line);
+
+	BAIL_OUT("could not parse pg_config --version output: $version_line")
+		unless defined $self->{_pg_version};
+}
+
+# Private routine which returns a reference to an array of integers
+# representing the pg_version of a PostgresNode, or parsed from a postgres
+# version string.  Development versions (such as "14devel") are converted
+# to an array with minus one as the last value (such as [14, -1]).
+#
+# For idempotency, will return the argument back to the caller if handed an
+# array reference.
+sub _pg_version_array
+{
+	my ($arg) = @_;
+
+	# accept node arguments
+	return _pg_version_array($arg->{_pg_version})
+		if (blessed($arg) && $arg->isa("PostgresNode"));
+
+	# idempotency
+	return $arg
+		if (ref($arg) && ref($arg) =~ /ARRAY/);
+
+	# Accept standard formats, in case caller has handed us the output of a
+	# postgres command line tool
+	$arg = $1
+		if ($arg =~ m/\(?PostgreSQL\)? (\d+(?:\.\d+)*(?:devel)?)/);
+
+	# Split into an array
+	my @result = split(/\./, $arg);
+
+	# Treat development versions as having a minor/micro version one less than
+	# the first released version of that branch.
+	if ($result[$#result] =~ m/^(\d+)devel$/)
+	{
+		pop(@result);
+		push(@result, $1, -1);
+	}
+
+	# Return an array reference
+	[ @result ];
+}
+
+# Private routine which compares the _pg_version_array obtained for the two
+# arguments and returns -1, 0, or 1, allowing comparison between two
+# PostgresNodes or a PostgresNode and a version string.
+#
+# To achieve intuitive behavior when comparing a PostgresNode against a version
+# string, "X" is equal to "X.Y" for any value of Y.  This allows calls like
+#
+#    $node->newer_than_version("14")
+#
+# to return true starting with "15devel", but false for "14devel", "14.0",
+# "14.1", etc.  It also allows
+#
+#    $node->at_least_version("14")
+#
+# to work for a node of version "14devel", where comparing against "14.0" would
+# fail.
+#
+sub _pg_version_cmp
+{
+	my ($a, $b) = @_;
+
+	$a = _pg_version_array($a);
+	$b = _pg_version_array($b);
+
+	for (my $idx = 0; ; $idx++)
+	{
+		return 0 unless (defined $a->[$idx] && defined $b->[$idx]);
+		return $a->[$idx] <=> $b->[$idx]
+			if ($a->[$idx] <=> $b->[$idx]);
+	}
+}
+
+=pod
+
+=item $node->older_than_version(other)
+
+Returns whether this node's postgres version is older than "other", which can be
+either another PostgresNode or a version string.
+
+=cut
+
+sub older_than_version
+{
+	my ($node, $pg_version) = @_;
+	return _pg_version_cmp($node->{_pg_version}, $pg_version) < 0;
+}
+
+=pod
+
+=item $node->newer_than_version(other)
+
+Returns whether this node's postgres version is newer than "other", which can
+be either another PostgresNode or a version string.
+
+=cut
+
+sub newer_than_version
+{
+	my ($node, $pg_version) = @_;
+	return _pg_version_cmp($node->{_pg_version}, $pg_version) > 0;
+}
+
+=pod
+
+=item $node->at_least_version(other)
+
+Returns whether this node's postgres version is at least as new as "other",
+which can be either another PostgresNode or a version string.
+
+=cut
+
+sub at_least_version
+{
+	my ($node, $pg_version) = @_;
+	return _pg_version_cmp($node->{_pg_version}, $pg_version) >= 0;
+}
+
 # Private routine to return a copy of the environment with the PATH and
 # (DY)LD_LIBRARY_PATH correctly set when there is an install path set for
 # the node.
-- 
2.21.1 (Apple Git-122.3)

