Skip to content

Commit 8c361e4

Browse files
committed
TAP: Add easier, more flexible ways to invoke psql
The PostgresNode::psql method is limited - it offers no access to the return code from psql, ignores SQL errors, and offers no access to psql's stderr. Provide a new psql_expert that addresses those limitations and can be used more flexibly - see the embedded PerlDoc for details. Also add a new psql_check method that invokes psql and dies if the SQL fails with any error. Test scripts should use this so they automatically die if SQL that should succeed fails instead; with the current psql method such failures would go undetected.
1 parent 8fd42e6 commit 8c361e4

File tree

1 file changed

+221
-12
lines changed

1 file changed

+221
-12
lines changed

src/test/perl/PostgresNode.pm

Lines changed: 221 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,20 @@ PostgresNode - class representing PostgreSQL server instance
2121
$node->restart('fast');
2222
2323
# run a query with psql
24-
# like: psql -qAXt postgres -c 'SELECT 1;'
25-
$psql_stdout = $node->psql('postgres', 'SELECT 1');
24+
# like:
25+
# echo 'SELECT 1' | psql -qAXt postgres -v ON_ERROR_STOP=1
26+
$psql_stdout = $node->psql_check('postgres', 'SELECT 1');
27+
28+
# Run psql with a timeout, capturing stdout and stderr
29+
# as well as the psql exit code. Pass some extra psql
30+
# options. If there's an error from psql raise an exception.
31+
my ($stdout, $stderr, $timed_out);
32+
my $cmdret = $psql_expert('postgres', 'SELECT pg_sleep(60)',
33+
stdout => \$stdout, stderr => \$stderr,
34+
timeout => 30, timed_out => \$timed_out,
35+
extra_params => ['--single-transaction'],
36+
on_error_die => 1)
37+
print "Sleep timed out" if $timed_out;
2638
2739
# run query every second until it returns 't'
2840
# or times out
@@ -69,6 +81,7 @@ use IPC::Run;
6981
use RecursiveCopy;
7082
use Test::More;
7183
use TestLib ();
84+
use Scalar::Util qw(blessed);
7285

7386
our @EXPORT = qw(
7487
get_new_node
@@ -780,11 +793,16 @@ sub teardown_node
780793
781794
=item $node->psql(dbname, sql)
782795
783-
Run a query with psql and return stdout, or on error print stderr.
796+
Run a query with psql and return stdout if psql returns with no error.
784797
785-
Executes a query/script with psql and returns psql's standard output. psql is
786-
run in unaligned tuples-only quiet mode with psqlrc disabled so simple queries
787-
will just return the result row(s) with fields separated by commas.
798+
psql is run in unaligned tuples-only quiet mode with psqlrc disabled so simple
799+
queries will just return the result row(s) with fields separated by commas.
800+
801+
If any stderr output is generated it is printed and discarded.
802+
803+
Nonzero return codes from psql are ignored and discarded.
804+
805+
Use psql_expert for more control.
788806
789807
=cut
790808

@@ -793,24 +811,215 @@ sub psql
793811
my ($self, $dbname, $sql) = @_;
794812

795813
my ($stdout, $stderr);
814+
796815
my $name = $self->name;
797816
print("### Running SQL command on node \"$name\": $sql\n");
798817

799-
IPC::Run::run [ 'psql', '-XAtq', '-d', $self->connstr($dbname), '-f',
800-
'-' ], '<', \$sql, '>', \$stdout, '2>', \$stderr
801-
or die;
818+
# Run the command, ignoring errors
819+
$self->psql_expert($dbname, $sql, stdout => \$stdout, stderr => \$stderr,
820+
on_error_die => 0, on_error_stop => 0);
821+
822+
if ($stderr ne "")
823+
{
824+
print "#### Begin standard error\n";
825+
print $stderr;
826+
print "\n#### End standard error\n";
827+
}
828+
return $stdout;
829+
}
830+
831+
=pod $node->psql_check($dbname, $sql) => stdout
832+
833+
Invoke 'psql' to run 'sql' on 'dbname' and return its stdout on success.
834+
Die if the SQL produces an error. Runs with ON_ERROR_STOP set.
835+
836+
Takes optional extra params like timeout and timed_out parameters with the same
837+
options as psql_expert.
802838
839+
=cut
840+
841+
sub psql_check
842+
{
843+
my ($self, $dbname, $sql, %params) = @_;
844+
845+
my ($stdout, $stderr);
846+
847+
my $ret = $self->psql_expert($dbname, $sql,
848+
%params,
849+
stdout => \$stdout, stderr => \$stderr,
850+
on_error_die => 1, on_error_stop => 1);
851+
852+
# psql can emit stderr from NOTICEs etc
803853
if ($stderr ne "")
804854
{
805855
print "#### Begin standard error\n";
806856
print $stderr;
807-
print "#### End standard error\n";
857+
print "\n#### End standard error\n";
808858
}
809-
chomp $stdout;
810-
$stdout =~ s/\r//g if $Config{osname} eq 'msys';
859+
811860
return $stdout;
812861
}
813862

863+
=pod $node->psql_expert($dbname, $sql, %params) => psql_retval
864+
865+
Invoke 'psql' to run 'sql' on 'dbname' and return the return value from
866+
psql, which is run with on_error_stop by default so that it will stop running
867+
sql and return 3 if the passed SQL results in an error.
868+
869+
psql is invoked in tuples-only unaligned mode with reading of psqlrc disabled. That
870+
may be overridden by passing extra psql parameters.
871+
872+
stdout and stderr are transformed to unix line endings if on Windows and any
873+
trailing newline is removed.
874+
875+
Dies on failure to invoke psql but not if psql exits with a nonzero return code
876+
(unless on_error_die specified). Dies if psql exits with a signal.
877+
878+
=over
879+
880+
=item stdout => \$stdout
881+
882+
If a scalar to write stdout to is passed as the keyword parameter 'stdout' it
883+
will be set to psql's stdout.
884+
885+
=item stderr => \$stderr
886+
887+
Same as 'stdout' but gets stderr. If the same scalar is passed for both stdout
888+
and stderr the results may be interleaved unpredictably.
889+
890+
=item on_error_stop => 1
891+
892+
By default psql_expert invokes psql with ON_ERROR_STOP=1 set so it will
893+
stop executing SQL at the first error and return exit code 2. If you want
894+
to ignore errors, pass 0 to on_error_stop.
895+
896+
=item on_error_die => 0
897+
898+
By default psql_expert returns psql's result code. Pass on_error_die to instead
899+
die with an informative message.
900+
901+
=item timeout => 'interval'
902+
903+
Set a timeout for the psql call as an interval accepted by IPC::Run::timer.
904+
Integer seconds is fine. psql_expert dies with an exception on timeout unless
905+
the timed_out parameter is passed.
906+
907+
=item timed_out => \$timed_out
908+
909+
Keyword parameter. Pass a scalar reference and it'll be set to true if the psql
910+
call timed out instead of dying. Has no effect unless 'timeout' is set.
911+
912+
=item extra_params => ['--single-transaction']
913+
914+
Pass additional parameters to psql. Must be an arrayref.
915+
916+
=back
917+
918+
e.g.
919+
920+
my ($stdout, $stderr, $timed_out);
921+
my $cmdret = $psql_expert('postgres', 'SELECT pg_sleep(60)',
922+
stdout => \$stdout, stderr => \$stderr,
923+
timeout => 30, timed_out => \$timed_out,
924+
extra_params => ['--single-transaction'])
925+
926+
will set $cmdret to undef and $timed_out to a true value.
927+
928+
$psql_expert('postgres', $sql, on_error_die => 1);
929+
930+
dies with an informative message if $sql fails.
931+
932+
=cut
933+
934+
sub psql_expert
935+
{
936+
my ($self, $dbname, $sql, %params) = @_;
937+
938+
my $stdout = $params{stdout};
939+
my $stderr = $params{stderr};
940+
my $timeout = undef;
941+
my $timeout_exception = 'psql timed out';
942+
my @psql_params = ('psql', '-XAtq', '-d', $self->connstr($dbname), '-f', '-');
943+
944+
$params{on_error_stop} = 1 unless defined $params{on_error_stop};
945+
$params{on_error_die} = 0 unless defined $params{on_error_die};
946+
947+
push @psql_params, '-v', 'ON_ERROR_STOP=1' if $params{on_error_stop};
948+
push @psql_params, @{$params{extra_params}} if defined $params{extra_params};
949+
950+
$timeout = IPC::Run::timeout( $params{timeout}, exception => $timeout_exception)
951+
if (defined($params{timeout}));
952+
953+
# IPC::Run would otherwise append to existing contents:
954+
$$stdout = "" if ref($stdout);
955+
$$stderr = "" if ref($stderr);
956+
957+
my $ret;
958+
959+
# Perl's exception handling is ... interesting. Especially since we have to
960+
# support 5.8.8. So we hide that from the caller, returning true/false for
961+
# timeout instead. See
962+
# http://search.cpan.org/~ether/Try-Tiny-0.24/lib/Try/Tiny.pm for
963+
# background.
964+
my $error = do {
965+
local $@;
966+
eval {
967+
IPC::Run::run \@psql_params, '<', \$sql, '>', $stdout, '2>', $stderr, $timeout;
968+
$ret = $?;
969+
};
970+
my $exc_save = $@;
971+
if ($exc_save) {
972+
# IPC::Run::run threw an exception. re-throw unless it's a
973+
# timeout, which we'll handle by testing is_expired
974+
if (blessed($exc_save) || $exc_save ne $timeout_exception) {
975+
print "Exception from IPC::Run::run when invoking psql: '$exc_save'\n";
976+
die $exc_save;
977+
} else {
978+
$ret = undef;
979+
980+
die "Got timeout exception '$exc_save' but timer not expired?!"
981+
unless $timeout->is_expired;
982+
983+
if (defined($params{timed_out}))
984+
{
985+
${$params{timed_out}} = 1;
986+
} else {
987+
die "psql timed out while running '@psql_params', stderr '$$stderr'";
988+
}
989+
}
990+
}
991+
};
992+
993+
chomp $$stdout;
994+
$$stdout =~ s/\r//g if $Config{osname} eq 'msys';
995+
996+
chomp $$stderr;
997+
$$stderr =~ s/\r//g if $Config{osname} eq 'msys';
998+
999+
# See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
1000+
# We don't use IPC::Run::Simple to limit dependencies.
1001+
#
1002+
# We always die on signal. If someone wants to capture signals
1003+
# to psql we can return it with another reference out parameter.
1004+
die "psql exited with signal " . ($ret & 127) . ": '$$stderr' while running '@psql_params'"
1005+
if $ret & 127;
1006+
die "psql exited with core dump: '$$stderr' while running '@psql_params'"
1007+
if $ret & 128;
1008+
$ret = $ret >> 8;
1009+
1010+
if ($ret && $params{on_error_die}) {
1011+
die "psql command line syntax error or internal error: '$$stderr' while running '@psql_params'"
1012+
if $ret == 1;
1013+
die "psql connection error: '$$stderr' while running '@psql_params'"
1014+
if $ret == 2;
1015+
die "error when running passed SQL: '$$stderr' while running '@psql_params'"
1016+
if $ret == 3;
1017+
die "unexpected error code $ret from psql: '$$stderr' while running '@psql_params'";
1018+
}
1019+
1020+
return $ret;
1021+
}
1022+
8141023
=pod
8151024
8161025
=item $node->poll_query_until(dbname, query)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy