diff options
Diffstat (limited to 'src/bin/psql/t')
-rw-r--r-- | src/bin/psql/t/001_basic.pl | 328 | ||||
-rw-r--r-- | src/bin/psql/t/010_tab_completion.pl | 452 | ||||
-rw-r--r-- | src/bin/psql/t/020_cancel.pl | 80 |
3 files changed, 860 insertions, 0 deletions
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl new file mode 100644 index 0000000..f447845 --- /dev/null +++ b/src/bin/psql/t/001_basic.pl @@ -0,0 +1,328 @@ + +# Copyright (c) 2021-2022, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; + +program_help_ok('psql'); +program_version_ok('psql'); +program_options_handling_ok('psql'); + +# Execute a psql command and check its output. +sub psql_like +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($node, $sql, $expected_stdout, $test_name) = @_; + + my ($ret, $stdout, $stderr) = $node->psql('postgres', $sql); + + is($ret, 0, "$test_name: exit code 0"); + is($stderr, '', "$test_name: no stderr"); + like($stdout, $expected_stdout, "$test_name: matches"); + + return; +} + +# Execute a psql command and check that it fails and check the stderr. +sub psql_fails_like +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + + my ($node, $sql, $expected_stderr, $test_name) = @_; + + # Use the context of a WAL sender, some of the tests rely on that. + my ($ret, $stdout, $stderr) = + $node->psql('postgres', $sql, replication => 'database'); + + isnt($ret, 0, "$test_name: exit code not 0"); + like($stderr, $expected_stderr, "$test_name: matches"); + + return; +} + +# test --help=foo, analogous to program_help_ok() +foreach my $arg (qw(commands variables)) +{ + my ($stdout, $stderr); + my $result; + + $result = IPC::Run::run [ 'psql', "--help=$arg" ], '>', \$stdout, '2>', + \$stderr; + ok($result, "psql --help=$arg exit code 0"); + isnt($stdout, '', "psql --help=$arg goes to stdout"); + is($stderr, '', "psql --help=$arg nothing to stderr"); +} + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init(extra => [ '--locale=C', '--encoding=UTF8' ]); +$node->append_conf( + 'postgresql.conf', q{ +wal_level = 'logical' +max_replication_slots = 4 +max_wal_senders = 4 +}); +$node->start; + +psql_like($node, '\copyright', qr/Copyright/, '\copyright'); +psql_like($node, '\help', qr/ALTER/, '\help without arguments'); +psql_like($node, '\help SELECT', qr/SELECT/, '\help with argument'); + +# Test clean handling of unsupported replication command responses +psql_fails_like( + $node, + 'START_REPLICATION 0/0', + qr/unexpected PQresultStatus: 8$/, + 'handling of unexpected PQresultStatus'); + +# test \timing +psql_like( + $node, + '\timing on +SELECT 1', + qr/^1$ +^Time: \d+[.,]\d\d\d ms/m, + '\timing with successful query'); + +# test \timing with query that fails +{ + my ($ret, $stdout, $stderr) = + $node->psql('postgres', "\\timing on\nSELECT error"); + isnt($ret, 0, '\timing with query error: query failed'); + like( + $stdout, + qr/^Time: \d+[.,]\d\d\d ms/m, + '\timing with query error: timing output appears'); + unlike( + $stdout, + qr/^Time: 0[.,]000 ms/m, + '\timing with query error: timing was updated'); +} + +# test that ENCODING variable is set and that it is updated when +# client encoding is changed +psql_like( + $node, + '\echo :ENCODING +set client_encoding = LATIN1; +\echo :ENCODING', + qr/^UTF8$ +^LATIN1$/m, + 'ENCODING variable is set and updated'); + +# test LISTEN/NOTIFY +psql_like( + $node, + 'LISTEN foo; +NOTIFY foo;', + qr/^Asynchronous notification "foo" received from server process with PID \d+\.$/, + 'notification'); + +psql_like( + $node, + "LISTEN foo; +NOTIFY foo, 'bar';", + qr/^Asynchronous notification "foo" with payload "bar" received from server process with PID \d+\.$/, + 'notification with payload'); + +# test behavior and output on server crash +my ($ret, $out, $err) = $node->psql('postgres', + "SELECT 'before' AS running;\n" + . "SELECT pg_terminate_backend(pg_backend_pid());\n" + . "SELECT 'AFTER' AS not_running;\n"); + +is($ret, 2, 'server crash: psql exit code'); +like($out, qr/before/, 'server crash: output before crash'); +ok($out !~ qr/AFTER/, 'server crash: no output after crash'); +is( $err, + 'psql:<stdin>:2: FATAL: terminating connection due to administrator command +psql:<stdin>:2: server closed the connection unexpectedly + This probably means the server terminated abnormally + before or while processing the request. +psql:<stdin>:2: error: connection to server was lost', + 'server crash: error message'); + +# test \errverbose +# +# (This is not in the regular regression tests because the output +# contains the source code location and we don't want to have to +# update that every time it changes.) + +psql_like( + $node, + 'SELECT 1; +\errverbose', + qr/^1\nThere is no previous error\.$/, + '\errverbose with no previous error'); + +# There are three main ways to run a query that might affect +# \errverbose: The normal way, using a cursor by setting FETCH_COUNT, +# and using \gdesc. Test them all. + +like( + ( $node->psql( + 'postgres', + "SELECT error;\n\\errverbose", + on_error_stop => 0))[2], + qr/\A^psql:<stdin>:1: ERROR: .*$ +^LINE 1: SELECT error;$ +^ *^.*$ +^psql:<stdin>:2: error: ERROR: [0-9A-Z]{5}: .*$ +^LINE 1: SELECT error;$ +^ *^.*$ +^LOCATION: .*$/m, + '\errverbose after normal query with error'); + +like( + ( $node->psql( + 'postgres', + "\\set FETCH_COUNT 1\nSELECT error;\n\\errverbose", + on_error_stop => 0))[2], + qr/\A^psql:<stdin>:2: ERROR: .*$ +^LINE 2: SELECT error;$ +^ *^.*$ +^psql:<stdin>:3: error: ERROR: [0-9A-Z]{5}: .*$ +^LINE 2: SELECT error;$ +^ *^.*$ +^LOCATION: .*$/m, + '\errverbose after FETCH_COUNT query with error'); + +like( + ( $node->psql( + 'postgres', + "SELECT error\\gdesc\n\\errverbose", + on_error_stop => 0))[2], + qr/\A^psql:<stdin>:1: ERROR: .*$ +^LINE 1: SELECT error$ +^ *^.*$ +^psql:<stdin>:2: error: ERROR: [0-9A-Z]{5}: .*$ +^LINE 1: SELECT error$ +^ *^.*$ +^LOCATION: .*$/m, + '\errverbose after \gdesc with error'); + +# Check behavior when using multiple -c and -f switches. +# Note that we cannot test backend-side errors as tests are unstable in this +# case: IPC::Run can complain about a SIGPIPE if psql quits before reading a +# query result. +my $tempdir = PostgreSQL::Test::Utils::tempdir; +$node->safe_psql('postgres', "CREATE TABLE tab_psql_single (a int);"); + +# Tests with ON_ERROR_STOP. +$node->command_ok( + [ + 'psql', '-X', + '--single-transaction', '-v', + 'ON_ERROR_STOP=1', '-c', + 'INSERT INTO tab_psql_single VALUES (1)', '-c', + 'INSERT INTO tab_psql_single VALUES (2)' + ], + 'ON_ERROR_STOP, --single-transaction and multiple -c switches'); +my $row_count = + $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single'); +is($row_count, '2', + '--single-transaction commits transaction, ON_ERROR_STOP and multiple -c switches' +); + +$node->command_fails( + [ + 'psql', '-X', + '--single-transaction', '-v', + 'ON_ERROR_STOP=1', '-c', + 'INSERT INTO tab_psql_single VALUES (3)', '-c', + "\\copy tab_psql_single FROM '$tempdir/nonexistent'" + ], + 'ON_ERROR_STOP, --single-transaction and multiple -c switches, error'); +$row_count = + $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single'); +is($row_count, '2', + 'client-side error rolls back transaction, ON_ERROR_STOP and multiple -c switches' +); + +# Tests mixing files and commands. +my $copy_sql_file = "$tempdir/tab_copy.sql"; +my $insert_sql_file = "$tempdir/tab_insert.sql"; +append_to_file($copy_sql_file, + "\\copy tab_psql_single FROM '$tempdir/nonexistent';"); +append_to_file($insert_sql_file, 'INSERT INTO tab_psql_single VALUES (4);'); +$node->command_ok( + [ + 'psql', '-X', '--single-transaction', '-v', + 'ON_ERROR_STOP=1', '-f', $insert_sql_file, '-f', + $insert_sql_file + ], + 'ON_ERROR_STOP, --single-transaction and multiple -f switches'); +$row_count = + $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single'); +is($row_count, '4', + '--single-transaction commits transaction, ON_ERROR_STOP and multiple -f switches' +); + +$node->command_fails( + [ + 'psql', '-X', '--single-transaction', '-v', + 'ON_ERROR_STOP=1', '-f', $insert_sql_file, '-f', + $copy_sql_file + ], + 'ON_ERROR_STOP, --single-transaction and multiple -f switches, error'); +$row_count = + $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single'); +is($row_count, '4', + 'client-side error rolls back transaction, ON_ERROR_STOP and multiple -f switches' +); + +# Tests without ON_ERROR_STOP. +# The last switch fails on \copy. The command returns a failure and the +# transaction commits. +$node->command_fails( + [ + 'psql', '-X', + '--single-transaction', '-f', + $insert_sql_file, '-f', + $insert_sql_file, '-c', + "\\copy tab_psql_single FROM '$tempdir/nonexistent'" + ], + 'no ON_ERROR_STOP, --single-transaction and multiple -f/-c switches'); +$row_count = + $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single'); +is($row_count, '6', + 'client-side error commits transaction, no ON_ERROR_STOP and multiple -f/-c switches' +); + +# The last switch fails on \copy coming from an input file. The command +# returns a success and the transaction commits. +$node->command_ok( + [ + 'psql', '-X', '--single-transaction', '-f', + $insert_sql_file, '-f', $insert_sql_file, '-f', + $copy_sql_file + ], + 'no ON_ERROR_STOP, --single-transaction and multiple -f switches'); +$row_count = + $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single'); +is($row_count, '8', + 'client-side error commits transaction, no ON_ERROR_STOP and multiple -f switches' +); + +# The last switch makes the command return a success, and the contents of +# the transaction commit even if there is a failure in-between. +$node->command_ok( + [ + 'psql', '-X', + '--single-transaction', '-c', + 'INSERT INTO tab_psql_single VALUES (5)', '-f', + $copy_sql_file, '-c', + 'INSERT INTO tab_psql_single VALUES (6)' + ], + 'no ON_ERROR_STOP, --single-transaction and multiple -c switches'); +$row_count = + $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single'); +is($row_count, '10', + 'client-side error commits transaction, no ON_ERROR_STOP and multiple -c switches' +); + +done_testing(); 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..2eea515 --- /dev/null +++ b/src/bin/psql/t/010_tab_completion.pl @@ -0,0 +1,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(); diff --git a/src/bin/psql/t/020_cancel.pl b/src/bin/psql/t/020_cancel.pl new file mode 100644 index 0000000..0265b86 --- /dev/null +++ b/src/bin/psql/t/020_cancel.pl @@ -0,0 +1,80 @@ + +# Copyright (c) 2021-2022, PostgreSQL Global Development Group + +use strict; +use warnings; + +use PostgreSQL::Test::Cluster; +use PostgreSQL::Test::Utils; +use Test::More; +use Time::HiRes qw(usleep); + +# Test query canceling by sending SIGINT to a running psql +# +# There is, as of this writing, no documented way to get the PID of +# the process from IPC::Run. As a workaround, we have psql print its +# own PID (which is the parent of the shell launched by psql) to a +# file. +if ($windows_os) +{ + plan skip_all => "cancel test requires a Unix shell"; +} + +my $tempdir = PostgreSQL::Test::Utils::tempdir; + +my $node = PostgreSQL::Test::Cluster->new('main'); +$node->init; +$node->start; + +local %ENV = $node->_get_env(); + +my ($stdin, $stdout, $stderr); + +# Test whether shell supports $PPID. It's part of POSIX, but some +# pre-/non-POSIX shells don't support it (e.g., NetBSD). +$stdin = "\\! echo \$PPID"; +IPC::Run::run([ 'psql', '-X', '-v', 'ON_ERROR_STOP=1' ], + '<', \$stdin, '>', \$stdout, '2>', \$stderr); +$stdout =~ /^\d+$/ or skip "shell apparently does not support \$PPID", 2; + +# Now start the real test +my $h = IPC::Run::start([ 'psql', '-X', '-v', 'ON_ERROR_STOP=1' ], + \$stdin, \$stdout, \$stderr); + +# Get the PID +$stdout = ''; +$stderr = ''; +$stdin = "\\! echo \$PPID >$tempdir/psql.pid\n"; +pump $h while length $stdin; +my $count; +my $psql_pid; +until ( + -s "$tempdir/psql.pid" + and ($psql_pid = + PostgreSQL::Test::Utils::slurp_file("$tempdir/psql.pid")) =~ + /^\d+\n/s) +{ + ($count++ < 100 * $PostgreSQL::Test::Utils::timeout_default) + or die "pid file did not appear"; + usleep(10_000); +} + +# Send sleep command and wait until the server has registered it +$stdin = "select pg_sleep($PostgreSQL::Test::Utils::timeout_default);\n"; +pump $h while length $stdin; +$node->poll_query_until('postgres', + q{SELECT (SELECT count(*) FROM pg_stat_activity WHERE query ~ '^select pg_sleep') > 0;} +) or die "timed out"; + +# Send cancel request +kill 'INT', $psql_pid; + +my $result = finish $h; + +ok(!$result, 'query failed as expected'); +like( + $stderr, + qr/canceling statement due to user request/, + 'query was canceled'); + +done_testing(); |