1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
|
# Copyright (c) 2021-2022, PostgreSQL Global Development Group
use strict;
use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
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 = PostgreSQL::Test::Cluster->new('main');
$node->init;
$node->start;
# set up a few database objects
$node->safe_psql('postgres',
"CREATE TABLE tab1 (c1 int primary key, c2 text);\n"
. "CREATE TABLE mytab123 (f1 int, f2 text);\n"
. "CREATE TABLE mytab246 (f1 int, f2 text);\n"
. "CREATE TABLE \"mixedName\" (f1 int, f2 text);\n"
. "CREATE TYPE enum1 AS ENUM ('foo', 'bar', 'baz', 'BLACK');\n"
. "CREATE PUBLICATION some_publication;\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 = "${PostgreSQL::Test::Utils::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($PostgreSQL::Test::Utils::timeout_default);
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($PostgreSQL::Test::Utils::timeout_default);
# 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
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
check_completion("\\r\n", qr/Query buffer reset.*postgres=# /s,
"\\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
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
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 handling of quoted names
check_completion(
"select * from \"my\t",
qr/select \* from "my\a?tab/,
"complete \"my<tab> to \"mytab when there are multiple choices");
check_completion(
"\t\t",
qr/"mytab123" +"mytab246"/,
"offer multiple quoted table choices");
# note: broken versions of libedit want to backslash the closing quote;
# not much we can do about that
check_completion("2\t", qr/246\\?" /,
"finish completion of one of multiple quoted table choices");
# note: broken versions of libedit may leave us in a state where psql
# thinks there's an unclosed double quote, so that we have to use
# clear_line not clear_query here
clear_line();
# check handling of mixed-case names
# note: broken versions of libedit want to backslash the closing quote;
# not much we can do about that
check_completion(
"select * from \"mi\t",
qr/"mixedName\\?" /,
"complete a mixed-case name");
# as above, must use clear_line not clear_query here
clear_line();
# check case folding
check_completion("select * from TAB\t", qr/tab1 /, "automatically fold case");
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");
# broken versions of libedit require clear_line not clear_query here
clear_line();
# check completion of a schema-qualified name
check_completion("select * from pub\t",
qr/public\./, "complete schema when relevant");
check_completion("tab\t", qr/tab1 /, "complete schema-qualified name");
clear_query();
check_completion(
"select * from PUBLIC.t\t",
qr/public\.tab1 /,
"automatically fold case in schema-qualified name");
clear_query();
# check interpretation of referenced names
check_completion(
"alter table tab1 drop constraint \t",
qr/tab1_pkey /,
"complete index name for referenced table");
clear_query();
check_completion(
"alter table TAB1 drop constraint \t",
qr/tab1_pkey /,
"complete index name for referenced table, with downcasing");
clear_query();
check_completion(
"alter table public.\"tab1\" drop constraint \t",
qr/tab1_pkey /,
"complete index name for referenced table, with schema and quoting");
clear_query();
# check variant where we're completing a qualified name from a refname
# (this one also checks successful completion in a multiline command)
check_completion(
"comment on constraint tab1_pkey \n on public.\t",
qr/public\.tab1/,
"complete qualified name from object reference");
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");
# broken versions of libedit require clear_line not clear_query here
clear_line();
# 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();
# check enum label completion
# some versions of readline/libedit require two tabs here, some only need one
# also, some versions will offer quotes, some will not
check_completion(
"ALTER TYPE enum1 RENAME VALUE 'ba\t\t",
qr|'?bar'? +'?baz'?|,
"offer multiple enum choices");
clear_line();
# enum labels are case sensitive, so this should complete BLACK immediately
check_completion(
"ALTER TYPE enum1 RENAME VALUE 'B\t",
qr|BLACK|,
"enum labels are case sensitive");
clear_line();
# check timezone name completion
check_completion("SET timezone TO am\t",
qr|'America/|, "offer partial timezone name");
check_completion("new_\t", qr|New_York|, "complete partial timezone name");
clear_line();
# check completion of a keyword offered in addition to object names;
# such a keyword should obey COMP_KEYWORD_CASE
foreach (
[ 'lower', 'CO', 'column' ],
[ 'upper', 'co', 'COLUMN' ],
[ 'preserve-lower', 'co', 'column' ],
[ 'preserve-upper', 'CO', 'COLUMN' ],)
{
my ($case, $in, $out) = @$_;
check_completion(
"\\set COMP_KEYWORD_CASE $case\n",
qr/postgres=#/,
"set completion case to '$case'");
check_completion("alter table tab1 rename $in\t\t\t",
qr|$out|,
"offer keyword $out for input $in<TAB>, COMP_KEYWORD_CASE = $case");
clear_query();
}
# alternate path where keyword comes from SchemaQuery
check_completion(
"DROP TYPE big\t",
qr/DROP TYPE bigint /,
"offer keyword from SchemaQuery");
clear_query();
# check create_command_generator
check_completion(
"CREATE TY\t",
qr/CREATE TYPE /,
"check create_command_generator");
clear_query();
# check words_after_create infrastructure
check_completion(
"CREATE TABLE mytab\t\t",
qr/mytab123 +mytab246/,
"check words_after_create");
clear_query();
# check VersionedQuery infrastructure
check_completion(
"DROP PUBLIC\t \t\t",
qr/DROP PUBLICATION\s+some_publication /,
"check VersionedQuery");
clear_query();
# hits ends_with() and logic for completing in multi-line queries
check_completion("analyze (\n\t\t", qr/VERBOSE/,
"check ANALYZE (VERBOSE ...");
clear_query();
# check completions for GUCs
check_completion(
"set interval\t\t",
qr/intervalstyle TO/,
"complete a GUC name");
check_completion(" iso\t", qr/iso_8601 /, "complete a GUC enum value");
clear_query();
# same, for qualified GUC names
check_completion(
"DO \$\$begin end\$\$ LANGUAGE plpgsql;\n",
qr/postgres=# /,
"load plpgsql extension");
check_completion("set plpg\t", qr/plpg\a?sql\./,
"complete prefix of a GUC name");
check_completion(
"var\t\t",
qr/variable_conflict TO/,
"complete a qualified GUC name");
check_completion(" USE_C\t",
qr/use_column/, "complete a qualified GUC enum value");
clear_query();
# check completions for psql variables
check_completion("\\set VERB\t", qr/VERBOSITY /,
"complete a psql variable name");
check_completion("def\t", qr/default /, "complete a psql variable value");
clear_query();
check_completion(
"\\echo :VERB\t",
qr/:VERBOSITY /,
"complete an interpolated psql variable name");
clear_query();
# check no-completions code path
check_completion("blarg \t\t", qr//, "check completion failure path");
clear_query();
# send psql an explicit \q to shut it down, else pty won't close properly
$timer->start($PostgreSQL::Test::Utils::timeout_default);
$in .= "\\q\n";
finish $h or die "psql returned $?";
$timer->reset;
# done
$node->stop;
done_testing();
|