summaryrefslogtreecommitdiffstats
path: root/src/bin/psql/t
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/psql/t')
-rw-r--r--src/bin/psql/t/010_tab_completion.pl227
1 files changed, 227 insertions, 0 deletions
diff --git a/src/bin/psql/t/010_tab_completion.pl b/src/bin/psql/t/010_tab_completion.pl
new file mode 100644
index 0000000..c27f216
--- /dev/null
+++ b/src/bin/psql/t/010_tab_completion.pl
@@ -0,0 +1,227 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More;
+use IPC::Run qw(pump finish timer);
+use Data::Dumper;
+
+# Do nothing unless Makefile has told us that the build is --with-readline.
+if (!defined($ENV{with_readline}) || $ENV{with_readline} ne 'yes')
+{
+ plan skip_all => 'readline is not supported by this build';
+}
+
+# Also, skip if user has set environment variable to command that.
+# This is mainly intended to allow working around some of the more broken
+# versions of libedit --- some users might find them acceptable even if
+# they won't pass these tests.
+if (defined($ENV{SKIP_READLINE_TESTS}))
+{
+ plan skip_all => 'SKIP_READLINE_TESTS is set';
+}
+
+# If we don't have IO::Pty, forget it, because IPC::Run depends on that
+# to support pty connections
+eval { require IO::Pty; };
+if ($@)
+{
+ plan skip_all => 'IO::Pty is needed to run this test';
+}
+
+# start a new server
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+
+# set up a few database objects
+$node->safe_psql('postgres',
+ "CREATE TABLE tab1 (f1 int, f2 text);\n"
+ . "CREATE TABLE mytab123 (f1 int, f2 text);\n"
+ . "CREATE TABLE mytab246 (f1 int, f2 text);\n");
+
+# Developers would not appreciate this test adding a bunch of junk to
+# their ~/.psql_history, so be sure to redirect history into a temp file.
+# We might as well put it in the test log directory, so that buildfarm runs
+# capture the result for possible debugging purposes.
+my $historyfile = "${TestLib::log_path}/010_psql_history.txt";
+$ENV{PSQL_HISTORY} = $historyfile;
+
+# Another pitfall for developers is that they might have a ~/.inputrc
+# file that changes readline's behavior enough to affect this test.
+# So ignore any such file.
+$ENV{INPUTRC} = '/dev/null';
+
+# Unset $TERM so that readline/libedit won't use any terminal-dependent
+# escape sequences; that leads to way too many cross-version variations
+# in the output.
+delete $ENV{TERM};
+# Some versions of readline inspect LS_COLORS, so for luck unset that too.
+delete $ENV{LS_COLORS};
+
+# In a VPATH build, we'll be started in the source directory, but we want
+# to run in the build directory so that we can use relative paths to
+# access the tmp_check subdirectory; otherwise the output from filename
+# completion tests is too variable.
+if ($ENV{TESTDIR})
+{
+ chdir $ENV{TESTDIR} or die "could not chdir to \"$ENV{TESTDIR}\": $!";
+}
+
+# Create some junk files for filename completion testing.
+my $FH;
+open $FH, ">", "tmp_check/somefile"
+ or die("could not create file \"tmp_check/somefile\": $!");
+print $FH "some stuff\n";
+close $FH;
+open $FH, ">", "tmp_check/afile123"
+ or die("could not create file \"tmp_check/afile123\": $!");
+print $FH "more stuff\n";
+close $FH;
+open $FH, ">", "tmp_check/afile456"
+ or die("could not create file \"tmp_check/afile456\": $!");
+print $FH "other stuff\n";
+close $FH;
+
+# fire up an interactive psql session
+my $in = '';
+my $out = '';
+
+my $timer = timer(5);
+
+my $h = $node->interactive_psql('postgres', \$in, \$out, $timer);
+
+like($out, qr/psql/, "print startup banner");
+
+# Simple test case: type something and see if psql responds as expected
+sub check_completion
+{
+ my ($send, $pattern, $annotation) = @_;
+
+ # report test failures from caller location
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ # reset output collector
+ $out = "";
+ # restart per-command timer
+ $timer->start(5);
+ # send the data to be sent
+ $in .= $send;
+ # wait ...
+ pump $h until ($out =~ $pattern || $timer->is_expired);
+ my $okay = ($out =~ $pattern && !$timer->is_expired);
+ ok($okay, $annotation);
+ # for debugging, log actual output if it didn't match
+ local $Data::Dumper::Terse = 1;
+ local $Data::Dumper::Useqq = 1;
+ diag 'Actual output was ' . Dumper($out) . "Did not match \"$pattern\"\n"
+ if !$okay;
+ return;
+}
+
+# Clear query buffer to start over
+# (won't work if we are inside a string literal!)
+sub clear_query
+{
+ check_completion("\\r\n", qr/postgres=# /, "\\r works");
+ return;
+}
+
+# Clear current line to start over
+# (this will work in an incomplete string literal, but it's less desirable
+# than clear_query because we lose evidence in the history file)
+sub clear_line
+{
+ check_completion("\025\n", qr/postgres=# /, "control-U works");
+ return;
+}
+
+# check basic command completion: SEL<tab> produces SELECT<space>
+check_completion("SEL\t", qr/SELECT /, "complete SEL<tab> to SELECT");
+
+clear_query();
+
+# check case variation is honored
+check_completion("sel\t", qr/select /, "complete sel<tab> to select");
+
+# check basic table name completion
+check_completion("* from t\t", qr/\* from tab1 /, "complete t<tab> to tab1");
+
+clear_query();
+
+# check table name completion with multiple alternatives
+# note: readline might print a bell before the completion
+check_completion(
+ "select * from my\t",
+ qr/select \* from my\a?tab/,
+ "complete my<tab> to mytab when there are multiple choices");
+
+# some versions of readline/libedit require two tabs here, some only need one
+check_completion(
+ "\t\t",
+ qr/mytab123 +mytab246/,
+ "offer multiple table choices");
+
+check_completion("2\t", qr/246 /,
+ "finish completion of one of multiple table choices");
+
+clear_query();
+
+# check case-sensitive keyword replacement
+# note: various versions of readline/libedit handle backspacing
+# differently, so just check that the replacement comes out correctly
+check_completion("\\DRD\t", qr/drds /, "complete \\DRD<tab> to \\drds");
+
+clear_query();
+
+# check filename completion
+check_completion(
+ "\\lo_import tmp_check/some\t",
+ qr|tmp_check/somefile |,
+ "filename completion with one possibility");
+
+clear_query();
+
+# note: readline might print a bell before the completion
+check_completion(
+ "\\lo_import tmp_check/af\t",
+ qr|tmp_check/af\a?ile|,
+ "filename completion with multiple possibilities");
+
+clear_query();
+
+# COPY requires quoting
+# note: broken versions of libedit want to backslash the closing quote;
+# not much we can do about that
+check_completion(
+ "COPY foo FROM tmp_check/some\t",
+ qr|'tmp_check/somefile\\?' |,
+ "quoted filename completion with one possibility");
+
+clear_line();
+
+check_completion(
+ "COPY foo FROM tmp_check/af\t",
+ qr|'tmp_check/afile|,
+ "quoted filename completion with multiple possibilities");
+
+# some versions of readline/libedit require two tabs here, some only need one
+# also, some will offer the whole path name and some just the file name
+# the quotes might appear, too
+check_completion(
+ "\t\t",
+ qr|afile123'? +'?(tmp_check/)?afile456|,
+ "offer multiple file choices");
+
+clear_line();
+
+# send psql an explicit \q to shut it down, else pty won't close properly
+$timer->start(5);
+$in .= "\\q\n";
+finish $h or die "psql returned $?";
+$timer->reset;
+
+# done
+$node->stop;
+done_testing();