citus/src/test/regress/pg_regress_multi.pl

695 lines
22 KiB
Perl
Executable File

#!/usr/bin/perl -w
#----------------------------------------------------------------------
#
# pg_regress_multi.pl - Test runner for Citus
#
# Portions Copyright (c) 2012-2016, Citus Data, Inc.
# Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
# Portions Copyright (c) 1994, Regents of the University of California
#
# src/test/regress/pg_regress_multi.pl
#
#----------------------------------------------------------------------
use strict;
use warnings;
use Fcntl;
use Getopt::Long;
use File::Spec::Functions;
use File::Path qw(make_path remove_tree);
use Config;
sub Usage()
{
print "pg_regress_multi - Citus test runner\n";
print "\n";
print "Usage:\n";
print " pg_regress_multi [MULTI OPTIONS] -- [PG REGRESS OPTS]\n";
print "\n";
print "Multi Options:\n";
print " --isolationtester Run isolationtester tests instead of plain tests\n";
print " --vanillatest Run postgres tests with citus loaded as shared preload library\n";
print " --bindir Path to postgres binary directory\n";
print " --libdir Path to postgres library directory\n";
print " --postgres-builddir Path to postgres build directory\n";
print " --postgres-srcdir Path to postgres build directory\n";
print " --pgxsdir Path to the PGXS directory\n";
print " --load-extension Extensions to install in all nodes\n";
print " --server-option Config option to pass to the server\n";
print " --valgrind Run server via valgrind\n";
print " --valgrind-path Path to the valgrind executable\n";
print " --valgrind-log-file Path to the write valgrind logs\n";
print " --pg_ctl-timeout Timeout for pg_ctl\n";
print " --connection-timeout Timeout for connecting to worker nodes\n";
exit 1;
}
# Option parsing
my $isolationtester = 0;
my $vanillatest = 0;
my $followercluster = 0;
my $bindir = "";
my $libdir = undef;
my $pgxsdir = "";
my $postgresBuilddir = "";
my $postgresSrcdir = "";
my $majorversion = "";
my @extensions = ();
my @userPgOptions = ();
my %dataTypes = ();
my %fdws = ();
my %fdwServers = ();
my %functions = ();
my %operators = ();
my $valgrind = 0;
my $valgrindPath = "valgrind";
my $valgrindLogFile = "valgrind_test_log.txt";
my $pgCtlTimeout = undef;
my $connectionTimeout = 5000;
my $serversAreShutdown = "TRUE";
my $usingWindows = 0;
if ($Config{osname} eq "MSWin32")
{
$usingWindows = 1;
};
GetOptions(
'isolationtester' => \$isolationtester,
'vanillatest' => \$vanillatest,
'follower-cluster' => \$followercluster,
'bindir=s' => \$bindir,
'libdir=s' => \$libdir,
'pgxsdir=s' => \$pgxsdir,
'postgres-builddir=s' => \$postgresBuilddir,
'postgres-srcdir=s' => \$postgresSrcdir,
'majorversion=s' => \$majorversion,
'load-extension=s' => \@extensions,
'server-option=s' => \@userPgOptions,
'valgrind' => \$valgrind,
'valgrind-path=s' => \$valgrindPath,
'valgrind-log-file=s' => \$valgrindLogFile,
'pg_ctl-timeout=s' => \$pgCtlTimeout,
'connection-timeout=s' => \$connectionTimeout,
'help' => sub { Usage() });
# Update environment to include [DY]LD_LIBRARY_PATH/LIBDIR/etc -
# pointing to the libdir - that's required so the right version of
# libpq, citus et al is being picked up.
#
# XXX: There's some issues with el capitan's SIP here, causing
# DYLD_LIBRARY_PATH not being inherited if SIP is enabled. That's a
# know problem, present in postgres itself as well.
if (defined $libdir)
{
$ENV{LD_LIBRARY_PATH} = "$libdir:".($ENV{LD_LIBRARY_PATH} || '');
$ENV{DYLD_LIBRARY_PATH} = "$libdir:".($ENV{DYLD_LIBRARY_PATH} || '');
$ENV{LIBPATH} = "$libdir:".($ENV{LIBPATH} || '');
$ENV{PATH} = "$libdir:".($ENV{PATH} || '');
}
# Put $bindir to the end of PATH. We want to prefer system binaries by
# default (as e.g. new libpq and old psql can cause issues), but still
# want to find binaries if they're not in PATH.
if (defined $bindir)
{
if ($usingWindows) {
$ENV{PATH} = ($ENV{PATH} || '').";$bindir";
} else {
$ENV{PATH} = ($ENV{PATH} || '').":$bindir";
}
}
# Most people are used to unified diffs these days, rather than the
# context diffs pg_regress defaults to. Change default to avoid
# everyone having to (re-)learn how to change that setting. Also add
# a bit more context to make it easier to locate failed test sections.
$ENV{PG_REGRESS_DIFF_OPTS} = '-dU10';
my $plainRegress = "";
my $isolationRegress = "";
if ($usingWindows)
{
$plainRegress = "$bindir\\pg_regress.exe";
$isolationRegress = "$bindir\\pg_isolation_regress.exe";
}
else
{
$plainRegress = "$pgxsdir/src/test/regress/pg_regress";
$isolationRegress = "${postgresBuilddir}/src/test/isolation/pg_isolation_regress";
}
if ($isolationtester && ! -f "$isolationRegress")
{
die <<"MESSAGE";
isolationtester not found at $isolationRegress.
isolationtester tests can only be run when source (detected as ${postgresSrcdir})
and build (detected as ${postgresBuilddir}) directory corresponding to $bindir
are present.
Additionally isolationtester in src/test/isolation needs to be built,
which it is not by default if tests have not been run. If the build
directory is present locally
"make -C ${postgresBuilddir} all" should do the trick.
MESSAGE
}
my $vanillaRegress = catfile("${postgresBuilddir}", "src", "test", "regress", "pg_regress");
if ($vanillatest && ! -f "$vanillaRegress")
{
die <<"MESSAGE";
pg_regress (for vanilla tests) not found at $vanillaRegress.
Vanilla tests can only be run when source (detected as ${postgresSrcdir})
and build (detected as ${postgresBuilddir}) directory corresponding to $bindir
are present.
MESSAGE
}
# If pgCtlTimeout is defined, we will set related environment variable.
# This is generally used with valgrind because valgrind starts slow and we
# need to increase timeout.
if (defined $pgCtlTimeout)
{
$ENV{PGCTLTIMEOUT} = "$pgCtlTimeout";
}
# We don't want valgrind to run pg_ctl itself, as that'd trigger a lot
# of spurious OS failures, e.g. in bash. So instead we have to replace
# the postgres binary with a wrapper that exec's valgrind, which in
# turn then executes postgres. That's unfortunately at the moment the
# only reliable way to do this.
sub replace_postgres
{
if (-e catfile("$bindir", "postgres.orig"))
{
print "wrapper exists\n";
}
else
{
print "moving $bindir/postgres to $bindir/postgres.orig\n";
rename catfile("$bindir", "postgres"), catfile("$bindir", "postgres.orig")
or die "Could not move postgres out of the way";
}
sysopen my $fh, catfile("$bindir", "postgres"), O_CREAT|O_TRUNC|O_RDWR, 0700
or die "Could not create postgres wrapper at $bindir/postgres";
print $fh <<"END";
#!/bin/bash
exec $valgrindPath \\
--quiet \\
--suppressions=${postgresSrcdir}/src/tools/valgrind.supp \\
--trace-children=yes --track-origins=yes --read-var-info=no \\
--leak-check=no \\
--error-markers=VALGRINDERROR-BEGIN,VALGRINDERROR-END \\
--log-file=$valgrindLogFile \\
$bindir/postgres.orig \\
"\$@"
END
close $fh;
}
# revert changes replace_postgres() performed
sub revert_replace_postgres
{
if (-e catfile("$bindir", "postgres.orig"))
{
print "wrapper exists, removing\n";
print "moving $bindir/postgres.orig to $bindir/postgres\n";
rename catfile("$bindir", "postgres.orig"), catfile("$bindir", "postgres")
or die "Could not move postgres back";
}
}
# always want to call initdb under normal postgres, so revert from a
# partial run, even if we're now not using valgrind.
revert_replace_postgres();
# Set some default configuration options
my $masterPort = 57636;
my $workerCount = 2;
my @workerPorts = ();
for (my $workerIndex = 1; $workerIndex <= $workerCount; $workerIndex++) {
my $workerPort = $masterPort + $workerIndex;
push(@workerPorts, $workerPort);
}
my $followerCoordPort = 9070;
my @followerWorkerPorts = ();
for (my $workerIndex = 1; $workerIndex <= $workerCount; $workerIndex++) {
my $workerPort = $followerCoordPort + $workerIndex;
push(@followerWorkerPorts, $workerPort);
}
my $host = "localhost";
my $user = "postgres";
my @pgOptions = ();
# Postgres options set for the tests
push(@pgOptions, '-c', "listen_addresses=${host}");
# not required, and we don't necessarily have access to the default directory
push(@pgOptions, '-c', "unix_socket_directories=");
push(@pgOptions, '-c', "fsync=off");
push(@pgOptions, '-c', "shared_preload_libraries=citus");
push(@pgOptions, '-c', "wal_level=logical");
# Citus options set for the tests
push(@pgOptions, '-c', "citus.shard_count=4");
push(@pgOptions, '-c', "citus.shard_max_size=1500kB");
push(@pgOptions, '-c', "citus.max_running_tasks_per_node=4");
push(@pgOptions, '-c', "citus.expire_cached_shards=on");
push(@pgOptions, '-c', "citus.task_tracker_delay=10ms");
push(@pgOptions, '-c', "citus.remote_task_check_interval=1ms");
push(@pgOptions, '-c', "citus.shard_replication_factor=2");
push(@pgOptions, '-c', "citus.node_connection_timeout=${connectionTimeout}");
if ($followercluster)
{
push(@pgOptions, '-c', "max_wal_senders=10");
push(@pgOptions, '-c', "hot_standby=on");
push(@pgOptions, '-c', "wal_level=replica");
}
# disable automatic distributed deadlock detection during the isolation testing
# to make sure that we always get consistent test outputs. If we don't manually
# (i.e., calling a UDF) detect the deadlocks, some sessions that do not participate
# in the deadlock may interleave with the deadlock detection, which results in non-
# consistent test outputs.
# since we have CREATE/DROP distributed tables very frequently, we also set
# shard_count to 4 to speed up the tests.
if($isolationtester)
{
push(@pgOptions, '-c', "citus.log_distributed_deadlock_detection=on");
push(@pgOptions, '-c', "citus.distributed_deadlock_detection_factor=-1");
push(@pgOptions, '-c', "citus.shard_count=4");
}
# Add externally added options last, so they overwrite the default ones above
for my $option (@userPgOptions)
{
push(@pgOptions, '-c', $option);
}
#define data types as a name->definition
%dataTypes = ('dummy_type', '(i integer)',
'order_side', ' ENUM (\'buy\', \'sell\')',
'test_composite_type', '(i integer, i2 integer)',
'bug_status', ' ENUM (\'new\', \'open\', \'closed\')');
# define functions as signature->definition
%functions = ('fake_fdw_handler()', 'fdw_handler AS \'citus\' LANGUAGE C STRICT;',
'equal_test_composite_type_function(test_composite_type, test_composite_type)',
'boolean AS \'select $1.i = $2.i AND $1.i2 = $2.i2;\' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT;');
%operators = ('=', '(LEFTARG = test_composite_type, RIGHTARG = test_composite_type, PROCEDURE = equal_test_composite_type_function, HASHES)');
#define fdws as name->handler name
%fdws = ('fake_fdw', 'fake_fdw_handler');
#define server_name->fdw
%fdwServers = ('fake_fdw_server', 'fake_fdw');
# Cleanup leftovers and prepare directories for the run
if (-e catfile('tmp_check', 'tmp-bin'))
{
remove_tree(catfile('tmp_check', 'tmp-bin')) or die "Could not remove tmp-bin directory";
}
if (-e catfile('tmp_check', 'master'))
{
remove_tree(catfile('tmp_check', 'master')) or die "Could not remove master directory";
}
for my $port (@workerPorts)
{
if (-e catfile("tmp_check", "worker.$port"))
{
remove_tree(catfile("tmp_check", "worker.$port")) or die "Could not remove worker directory";
}
}
if (-e catfile("tmp_check", "master-follower"))
{
remove_tree(catfile("tmp_check", "master-follower")) or die "Could not remove master directory";
}
for my $port (@followerWorkerPorts)
{
if (-e catfile("tmp_check", "follower.$port"))
{
remove_tree(catfile("tmp_check", "follower.$port")) or die "Could not remove worker directory";
}
}
# Prepare directory in which 'psql' has some helpful variables for locating the workers
make_path(catfile("tmp_check", "tmp-bin")) or die "Could not create tmp_bin directory $!\n";
my $psql_name = "psql";
if ($usingWindows)
{
$psql_name = "psql.cmd";
}
sysopen my $fh, catfile("tmp_check", "tmp-bin", $psql_name), O_CREAT|O_TRUNC|O_RDWR, 0700
or die "Could not create psql wrapper";
if ($usingWindows)
{
print $fh "\@echo off\n";
}
print $fh catfile($bindir, "psql")." ";
print $fh "--variable=master_port=$masterPort ";
print $fh "--variable=follower_master_port=$followerCoordPort ";
print $fh "--variable=default_user=$user ";
print $fh "--variable=SHOW_CONTEXT=always ";
for my $workeroff (0 .. $#workerPorts)
{
my $port = $workerPorts[$workeroff];
print $fh "--variable=worker_".($workeroff+1)."_port=$port ";
}
for my $workeroff (0 .. $#followerWorkerPorts)
{
my $port = $followerWorkerPorts[$workeroff];
print $fh "--variable=follower_worker_".($workeroff+1)."_port=$port ";
}
if ($usingWindows)
{
print $fh "--variable=dev_null=\"/nul\" ";
print $fh "--variable=temp_dir=\"%TEMP%\"";
}
else
{
print $fh "--variable=dev_null=\"/dev/null\" ";
print $fh "--variable=temp_dir=\"/tmp/\"";
}
if ($usingWindows)
{
print $fh " %*\n"; # pass on the commandline arguments
}
else
{
print $fh " \"\$@\"\n"; # pass on the commandline arguments
}
close $fh;
make_path(catfile('tmp_check', 'master', 'log')) or die 'Could not create master directory';
for my $port (@workerPorts)
{
make_path(catfile("tmp_check", "worker.$port", "log"))
or die "Could not create worker directory";
}
if ($followercluster)
{
make_path(catfile('tmp_check', 'master-follower', 'log')) or die "Could not create follower directory";
for my $port (@followerWorkerPorts)
{
make_path(catfile("tmp_check", "follower.$port", "log"))
or die "Could not create worker directory";
}
}
# Create new data directories, copy workers for speed
system(catfile("$bindir", "initdb"), ("--nosync", "-U", $user, "--encoding", "UTF8", catfile("tmp_check", "master", "data"))) == 0
or die "Could not create master data directory";
if ($followercluster)
{
# This is only necessary on PG 9.6 but it doesn't hurt PG 10
open(my $fd, ">>", catfile("tmp_check", "master", "data", "pg_hba.conf"))
or die "could not open pg_hba.conf";
print $fd "\nhost replication postgres 127.0.0.1/32 trust";
close $fd;
}
if ($usingWindows)
{
for my $port (@workerPorts)
{
system(catfile("$bindir", "initdb"), ("--nosync", "-U", $user, "--encoding", "UTF8", catfile("tmp_check", "worker.$port", "data"))) == 0
or die "Could not create worker data directory";
}
}
else
{
for my $port (@workerPorts)
{
system("cp", ("-a", catfile("tmp_check", "master", "data"), catfile("tmp_check", "worker.$port", "data"))) == 0
or die "Could not create worker data directory";
}
}
if ($usingWindows)
{
# takeown returns a failing result code no matter what happens, so skip the check
system("takeown /f data");
system("icacls data /grant \%userdomain\%\\\%username\%:F");
}
# Routine to shutdown servers at failure/exit
sub ShutdownServers()
{
if ($serversAreShutdown eq "FALSE")
{
system(catfile("$bindir", "pg_ctl"),
('stop', '-w', '-D', catfile('tmp_check', 'master', 'data'))) == 0
or warn "Could not shutdown worker server";
for my $port (@workerPorts)
{
system(catfile("$bindir", "pg_ctl"),
('stop', '-w', '-D', catfile("tmp_check", "worker.$port", "data"))) == 0
or warn "Could not shutdown worker server";
}
if ($followercluster)
{
system(catfile("$bindir", "pg_ctl"),
('stop', '-w', '-D', catfile('tmp_check', 'master-follower', 'data'))) == 0
or warn "Could not shutdown worker server";
for my $port (@followerWorkerPorts)
{
system(catfile("$bindir", "pg_ctl"),
('stop', '-w', '-D', catfile("tmp_check", "follower.$port", "data"))) == 0
or warn "Could not shutdown worker server";
}
}
$serversAreShutdown = "TRUE";
}
}
# Set signals to shutdown servers
$SIG{INT} = \&ShutdownServers;
$SIG{QUIT} = \&ShutdownServers;
$SIG{TERM} = \&ShutdownServers;
$SIG{__DIE__} = \&ShutdownServers;
# Shutdown servers on exit only if help option is not used
END
{
if ($? != 1)
{
ShutdownServers();
}
# At the end of a run, replace redirected binary with original again
if ($valgrind)
{
revert_replace_postgres();
}
}
# want to use valgrind, replace binary before starting server
if ($valgrind)
{
replace_postgres();
}
# Signal that servers should be shutdown
$serversAreShutdown = "FALSE";
# Start servers
if(system(catfile("$bindir", "pg_ctl"),
('start', '-w',
'-o', join(" ", @pgOptions)." -c port=$masterPort",
'-D', catfile('tmp_check', 'master', 'data'), '-l', catfile('tmp_check', 'master', 'log', 'postmaster.log'))) != 0)
{
system("tail", ("-n20", catfile("tmp_check", "master", "log", "postmaster.log")));
die "Could not start master server";
}
for my $port (@workerPorts)
{
if(system(catfile("$bindir", "pg_ctl"),
('start', '-w',
'-o', join(" ", @pgOptions)." -c port=$port",
'-D', catfile("tmp_check", "worker.$port", "data"),
'-l', catfile("tmp_check", "worker.$port", "log", "postmaster.log"))) != 0)
{
system("tail", ("-n20", catfile("tmp_check", "worker.$port", "log", "postmaster.log")));
die "Could not start worker server";
}
}
# Setup the follower nodes
if ($followercluster)
{
# This test would run faster on PG10 if we could pass --no-sync here but that flag
# isn't supported on PG 9.6. In a year when we drop support for PG9.6 add that flag!
system(catfile("$bindir", "pg_basebackup"),
("-D", catfile("tmp_check", "master-follower", "data"), "--host=$host", "--port=$masterPort",
"--username=$user", "-R", "-X", "stream")) == 0
or die 'could not take basebackup';
for my $offset (0 .. $#workerPorts)
{
my $workerPort = $workerPorts[$offset];
my $followerPort = $followerWorkerPorts[$offset];
system(catfile("$bindir", "pg_basebackup"),
("-D", catfile("tmp_check", "follower.$followerPort", "data"), "--host=$host", "--port=$workerPort",
"--username=$user", "-R", "-X", "stream")) == 0
or die "Could not take basebackup";
}
if(system(catfile("$bindir", "pg_ctl"),
('start', '-w',
'-o', join(" ", @pgOptions)." -c port=$followerCoordPort",
'-D', catfile('tmp_check', 'master-follower', 'data'), '-l', catfile('tmp_check', 'master-follower', 'log', 'postmaster.log'))) != 0)
{
system("tail", ("-n20", catfile("tmp_check", "master-follower", "log", "postmaster.log")));
die "Could not start master follower server";
}
for my $port (@followerWorkerPorts)
{
if(system(catfile("$bindir", "pg_ctl"),
('start', '-w',
'-o', join(" ", @pgOptions)." -c port=$port",
'-D', catfile("tmp_check", "follower.$port", "data"),
'-l', catfile("tmp_check", "follower.$port", "log", "postmaster.log"))) != 0)
{
system("tail", ("-n20", catfile("tmp_check", "follower.$port", "log", "postmaster.log")));
die "Could not start follower server";
}
}
}
###
# Create database, extensions, types, functions and fdws on the workers,
# pg_regress won't know to create them for us.
###
for my $port (@workerPorts)
{
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $port, '-U', $user, "-d", "postgres",
'-c', "CREATE DATABASE regression;")) == 0
or die "Could not create regression database on worker";
for my $extension (@extensions)
{
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $port, '-U', $user, "-d", "regression",
'-c', "CREATE EXTENSION IF NOT EXISTS $extension;")) == 0
or die "Could not create extension on worker";
}
foreach my $dataType (keys %dataTypes)
{
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $port, '-U', $user, "-d", "regression",
'-c', "CREATE TYPE $dataType AS $dataTypes{$dataType};")) == 0
or die "Could not create TYPE $dataType on worker";
}
foreach my $function (keys %functions)
{
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $port, '-U', $user, "-d", "regression",
'-c', "CREATE FUNCTION $function RETURNS $functions{$function};")) == 0
or die "Could not create FUNCTION $function on worker";
}
foreach my $operator (keys %operators)
{
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $port, '-U', $user, "-d", "regression",
'-c', "CREATE OPERATOR $operator $operators{$operator};")) == 0
or die "Could not create OPERATOR $operator on worker";
}
foreach my $fdw (keys %fdws)
{
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $port, '-U', $user, "-d", "regression",
'-c', "CREATE FOREIGN DATA WRAPPER $fdw HANDLER $fdws{$fdw};")) == 0
or die "Could not create foreign data wrapper $fdw on worker";
}
foreach my $fdwServer (keys %fdwServers)
{
system(catfile($bindir, "psql"),
('-X', '-h', $host, '-p', $port, '-U', $user, "-d", "regression",
'-c', "CREATE SERVER $fdwServer FOREIGN DATA WRAPPER $fdwServers{$fdwServer};")) == 0
or die "Could not create server $fdwServer on worker";
}
}
# Prepare pg_regress arguments
my @arguments = (
"--host", $host,
'--port', $masterPort,
'--user', $user,
# '--bindir', 'C:\Users\Administrator\Downloads\pg-64\bin',
'--bindir', catfile("tmp_check", "tmp-bin")
);
# Add load extension parameters to the argument list
for my $extension (@extensions)
{
push(@arguments, "--load-extension=$extension");
}
# Append remaining ARGV arguments to pg_regress arguments
push(@arguments, @ARGV);
my $startTime = time();
# Finally run the tests
if ($vanillatest)
{
$ENV{PGHOST} = $host;
$ENV{PGPORT} = $masterPort;
$ENV{PGUSER} = $user;
system("make", ("-C", catfile("$postgresBuilddir", "src", "test", "regress"), "installcheck-parallel")) == 0
or die "Could not run vanilla tests";
}
elsif ($isolationtester)
{
push(@arguments, "--dbname=regression");
system("$isolationRegress", @arguments) == 0
or die "Could not run isolation tests";
}
else
{
system("$plainRegress", @arguments) == 0
or die "Could not run regression tests";
}
my $endTime = time();
print "Finished in ". ($endTime - $startTime)." seconds. \n";
exit 0;