Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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)