Skip to content

Commit 7c01504

Browse files
committed
Add basic TAP tests for psql's tab-completion logic.
Up to now, psql's tab-complete.c has had exactly no regression test coverage. This patch is an experimental attempt to add some. This needs Perl's IO::Pty module, which isn't installed everywhere, so the test script just skips all tests if that's not present. There may be other portability gotchas too, so I await buildfarm results with interest. So far this just covers a few very basic keyword-completion and query-driven-completion scenarios, which should be enough to let us get a feel for whether this is practical at all from a portability standpoint. If it is, there's lots more that can be done. Discussion: https://postgr.es/m/10967.1577562752@sss.pgh.pa.us
1 parent 915c04f commit 7c01504

File tree

7 files changed

+204
-1
lines changed

7 files changed

+204
-1
lines changed

configure

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ with_libxml
706706
XML2_CONFIG
707707
UUID_EXTRA_OBJS
708708
with_uuid
709+
with_readline
709710
with_systemd
710711
with_selinux
711712
with_openssl
@@ -8000,6 +8001,7 @@ $as_echo "$as_me: WARNING: *** Readline does not work on MinGW --- disabling" >&
80008001
fi
80018002

80028003

8004+
80038005
#
80048006
# Prefer libedit
80058007
#

configure.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,7 @@ if test "$PORTNAME" = "win32"; then
875875
with_readline=no
876876
fi
877877
fi
878+
AC_SUBST(with_readline)
878879

879880

880881
#

src/Makefile.global.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ with_perl = @with_perl@
185185
with_python = @with_python@
186186
with_tcl = @with_tcl@
187187
with_openssl = @with_openssl@
188+
with_readline = @with_readline@
188189
with_selinux = @with_selinux@
189190
with_systemd = @with_systemd@
190191
with_gssapi = @with_gssapi@

src/bin/psql/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/psqlscanslash.c
22
/sql_help.h
33
/sql_help.c
4-
54
/psql
5+
/tmp_check/

src/bin/psql/Makefile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ subdir = src/bin/psql
1616
top_builddir = ../../..
1717
include $(top_builddir)/src/Makefile.global
1818

19+
# make this available to TAP test scripts
20+
export with_readline
21+
1922
REFDOCDIR= $(top_srcdir)/doc/src/sgml/ref
2023

2124
override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) $(CPPFLAGS)
@@ -73,8 +76,15 @@ uninstall:
7376

7477
clean distclean:
7578
rm -f psql$(X) $(OBJS) lex.backup
79+
rm -rf tmp_check
7680

7781
# files removed here are supposed to be in the distribution tarball,
7882
# so do not clean them in the clean/distclean rules
7983
maintainer-clean: distclean
8084
rm -f sql_help.h sql_help.c psqlscanslash.c
85+
86+
check:
87+
$(prove_check)
88+
89+
installcheck:
90+
$(prove_installcheck)

src/bin/psql/t/010_tab_completion.pl

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
use strict;
2+
use warnings;
3+
4+
use PostgresNode;
5+
use TestLib;
6+
use Test::More;
7+
use IPC::Run qw(pump finish timer);
8+
9+
if ($ENV{with_readline} ne 'yes')
10+
{
11+
plan skip_all => 'readline is not supported by this build';
12+
}
13+
14+
# If we don't have IO::Pty, forget it, because IPC::Run depends on that
15+
# to support pty connections
16+
eval { require IO::Pty; };
17+
if ($@)
18+
{
19+
plan skip_all => 'IO::Pty is needed to run this test';
20+
}
21+
22+
# start a new server
23+
my $node = get_new_node('main');
24+
$node->init;
25+
$node->start;
26+
27+
# set up a few database objects
28+
$node->safe_psql('postgres',
29+
"CREATE TABLE tab1 (f1 int, f2 text);\n"
30+
. "CREATE TABLE mytab123 (f1 int, f2 text);\n"
31+
. "CREATE TABLE mytab246 (f1 int, f2 text);\n");
32+
33+
# Developers would not appreciate this test adding a bunch of junk to
34+
# their ~/.psql_history, so be sure to redirect history into a temp file.
35+
# We might as well put it in the test log directory, so that buildfarm runs
36+
# capture the result for possible debugging purposes.
37+
my $historyfile = "${TestLib::log_path}/010_psql_history.txt";
38+
$ENV{PSQL_HISTORY} = $historyfile;
39+
40+
# fire up an interactive psql session
41+
my $in = '';
42+
my $out = '';
43+
44+
my $timer = timer(5);
45+
46+
my $h = $node->interactive_psql('postgres', \$in, \$out, $timer);
47+
48+
ok($out =~ /psql/, "print startup banner");
49+
50+
# Simple test case: type something and see if psql responds as expected
51+
sub check_completion
52+
{
53+
my ($send, $pattern, $annotation) = @_;
54+
55+
# reset output collector
56+
$out = "";
57+
# restart per-command timer
58+
$timer->start(5);
59+
# send the data to be sent
60+
$in .= $send;
61+
# wait ...
62+
pump $h until ($out =~ m/$pattern/ || $timer->is_expired);
63+
my $okay = ($out =~ m/$pattern/ && !$timer->is_expired);
64+
ok($okay, $annotation);
65+
# for debugging, log actual output if it didn't match
66+
note 'Actual output was "' . $out . "\"\n" if !$okay;
67+
}
68+
69+
# Clear query buffer to start over
70+
# (won't work if we are inside a string literal!)
71+
sub clear_query
72+
{
73+
check_completion("\\r\n", "postgres=# ", "\\r works");
74+
}
75+
76+
# check basic command completion: SEL<tab> produces SELECT<space>
77+
check_completion("SEL\t", "SELECT ", "complete SEL<tab> to SELECT");
78+
79+
clear_query();
80+
81+
# check case variation is honored
82+
check_completion("sel\t", "select ", "complete sel<tab> to select");
83+
84+
# check basic table name completion
85+
check_completion("* from t\t", "\\* from tab1 ", "complete t<tab> to tab1");
86+
87+
clear_query();
88+
89+
# check table name completion with multiple alternatives
90+
# note: readline might print a bell before the completion
91+
check_completion(
92+
"select * from my\t",
93+
"select \\* from my\a?tab",
94+
"complete my<tab> to mytab when there are multiple choices");
95+
96+
# some versions of readline/libedit require two tabs here, some only need one
97+
check_completion("\t\t", "mytab123 +mytab246",
98+
"offer multiple table choices");
99+
100+
check_completion("2\t", "246 ",
101+
"finish completion of one of multiple table choices");
102+
103+
clear_query();
104+
105+
# check case-sensitive keyword replacement
106+
# XXX the output here might vary across readline versions
107+
check_completion(
108+
"\\DRD\t",
109+
"\\DRD\b\b\bdrds ",
110+
"complete \\DRD<tab> to \\drds");
111+
112+
clear_query();
113+
114+
# send psql an explicit \q to shut it down, else pty won't close properly
115+
$timer->start(5);
116+
$in .= "\\q\n";
117+
finish $h or die "psql returned $?";
118+
$timer->reset;
119+
120+
# done
121+
$node->stop;
122+
done_testing();

src/test/perl/PostgresNode.pm

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,73 @@ sub psql
15341534

15351535
=pod
15361536
1537+
=item $node->interactive_psql($dbname, \$stdin, \$stdout, $timer, %params) => harness
1538+
1539+
Invoke B<psql> on B<$dbname> and return an IPC::Run harness object,
1540+
which the caller may use to send interactive input to B<psql>.
1541+
The process's stdin is sourced from the $stdin scalar reference,
1542+
and its stdout and stderr go to the $stdout scalar reference.
1543+
ptys are used so that psql thinks it's being called interactively.
1544+
1545+
The specified timer object is attached to the harness, as well.
1546+
It's caller's responsibility to select the timeout length, and to
1547+
restart the timer after each command if the timeout is per-command.
1548+
1549+
psql is invoked in tuples-only unaligned mode with reading of B<.psqlrc>
1550+
disabled. That may be overridden by passing extra psql parameters.
1551+
1552+
Dies on failure to invoke psql, or if psql fails to connect.
1553+
Errors occurring later are the caller's problem.
1554+
1555+
Be sure to "finish" the harness when done with it.
1556+
1557+
The only extra parameter currently accepted is
1558+
1559+
=over
1560+
1561+
=item extra_params => ['--single-transaction']
1562+
1563+
If given, it must be an array reference containing additional parameters to B<psql>.
1564+
1565+
=back
1566+
1567+
This requires IO::Pty in addition to IPC::Run.
1568+
1569+
=cut
1570+
1571+
sub interactive_psql
1572+
{
1573+
my ($self, $dbname, $stdin, $stdout, $timer, %params) = @_;
1574+
1575+
my @psql_params = ('psql', '-XAt', '-d', $self->connstr($dbname));
1576+
1577+
push @psql_params, @{ $params{extra_params} }
1578+
if defined $params{extra_params};
1579+
1580+
# Ensure there is no data waiting to be sent:
1581+
$$stdin = "" if ref($stdin);
1582+
# IPC::Run would otherwise append to existing contents:
1583+
$$stdout = "" if ref($stdout);
1584+
1585+
my $harness = IPC::Run::start \@psql_params,
1586+
'<pty<', $stdin, '>pty>', $stdout, $timer;
1587+
1588+
# Pump until we see psql's help banner. This ensures that callers
1589+
# won't write anything to the pty before it's ready, avoiding an
1590+
# implementation issue in IPC::Run. Also, it means that psql
1591+
# connection failures are caught here, relieving callers of
1592+
# the need to handle those. (Right now, we have no particularly
1593+
# good handling for errors anyway, but that might be added later.)
1594+
pump $harness
1595+
until $$stdout =~ /Type "help" for help/ || $timer->is_expired;
1596+
1597+
die "psql startup timed out" if $timer->is_expired;
1598+
1599+
return $harness;
1600+
}
1601+
1602+
=pod
1603+
15371604
=item $node->poll_query_until($dbname, $query [, $expected ])
15381605
15391606
Run B<$query> repeatedly, until it returns the B<$expected> result

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