psql - improve test coverage from 41% to 88%
Hello devs,
The attached patch improves psql code coverage by adding a specific TAP
test. The 1709 tests take 4 seconds CPU (6.3 elapsed time) on my laptop.
The infrastructure is updated to require perl module "Expect", allowing to
test interactive features such as tab completion and prompt changes.
Coverage in "src/bin/psql" jumps from 40.0% of lines and 41.9% of
functions to 78.4% of lines and 98.1% of functions with "check-world".
--
Fabien.
Attachments:
psql-tap-1.patchtext/x-diff; name=psql-tap-1.patchDownload
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index b7d36b65dd..aabc27ab6f 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -506,6 +506,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*checksum verification failed/s],
'pg_basebackup reports checksum mismatch');
@@ -526,6 +527,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt2" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*further.*failures.*will.not.be.reported/s],
'pg_basebackup does not report more than 5 checksum mismatches');
@@ -542,6 +544,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt3" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*7 total checksum verification failures/s],
'pg_basebackup correctly report the total number of checksum mismatches');
diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl
index 59228b916c..cf4811d382 100644
--- a/src/bin/pg_checksums/t/002_actions.pl
+++ b/src/bin/pg_checksums/t/002_actions.pl
@@ -62,6 +62,7 @@ sub check_relation_corruption
'--filenode', $relfilenode_corrupted
],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data for single relfilenode on tablespace $tablespace"
@@ -71,6 +72,7 @@ sub check_relation_corruption
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data on tablespace $tablespace");
@@ -203,6 +205,7 @@ sub fail_corrupt
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/^$/],
[qr/could not read block 0 in file.*$file\":/],
"fails for corrupted data in $file");
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
index 3b63ad230f..ebe9b80a52 100644
--- a/src/bin/pg_controldata/t/001_pg_controldata.pl
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -33,6 +33,7 @@ close $fh;
command_checks_all(
[ 'pg_controldata', $node->data_dir ],
0,
+ '',
[
qr/WARNING: Calculated CRC checksum does not match value stored in file/,
qr/WARNING: invalid WAL segment size/
diff --git a/src/bin/pg_resetwal/t/002_corrupted.pl b/src/bin/pg_resetwal/t/002_corrupted.pl
index f9940d7fc5..1990669d26 100644
--- a/src/bin/pg_resetwal/t/002_corrupted.pl
+++ b/src/bin/pg_resetwal/t/002_corrupted.pl
@@ -30,6 +30,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/
@@ -46,6 +47,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index b82d3f65c4..01010914fe 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -50,7 +50,7 @@ sub pgbench
push @cmd, @args;
- $node->command_checks_all(\@cmd, $stat, $out, $err, $name);
+ $node->command_checks_all(\@cmd, $stat, '', $out, $err, $name);
# cleanup?
#unlink @filenames or die "cannot unlink files (@filenames): $!";
diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl
index f7fa18418b..b58f3548c3 100644
--- a/src/bin/pgbench/t/002_pgbench_no_server.pl
+++ b/src/bin/pgbench/t/002_pgbench_no_server.pl
@@ -25,7 +25,7 @@ sub pgbench
my ($opts, $stat, $out, $err, $name) = @_;
print STDERR "opts=$opts, stat=$stat, out=$out, err=$err, name=$name";
command_checks_all([ 'pgbench', split(/\s+/, $opts) ],
- $stat, $out, $err, $name);
+ $stat, '', $out, $err, $name);
return;
}
@@ -52,7 +52,7 @@ sub pgbench_scripts
push @cmd, '-f', $filename;
}
}
- command_checks_all(\@cmd, $stat, $out, $err, $name);
+ command_checks_all(\@cmd, $stat, '', $out, $err, $name);
return;
}
diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore
index c2862b12d6..d324c1c1fa 100644
--- a/src/bin/psql/.gitignore
+++ b/src/bin/psql/.gitignore
@@ -3,3 +3,4 @@
/sql_help.c
/psql
+/tmp_check
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 69bb297fe7..9473ab01cb 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -60,8 +60,15 @@ uninstall:
clean distclean:
rm -f psql$(X) $(OBJS) lex.backup
+ rm -rf tmp_check
# files removed here are supposed to be in the distribution tarball,
# so do not clean them in the clean/distclean rules
maintainer-clean: distclean
rm -f sql_help.h sql_help.c psqlscanslash.c
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 774cc764ff..d7c0fc0c1e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -307,6 +307,7 @@ describeTablespaces(const char *pattern, bool verbose)
*
* a for aggregates
* n for normal
+ * p for procedure
* t for trigger
* w for window
*
diff --git a/src/bin/psql/t/001_psql.pl b/src/bin/psql/t/001_psql.pl
new file mode 100644
index 0000000000..dafa68e63e
--- /dev/null
+++ b/src/bin/psql/t/001_psql.pl
@@ -0,0 +1,869 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+my $node = get_new_node('main');
+$node->init();
+$node->start();
+
+# create a file under the test directory
+# return its full path
+sub create_test_file
+{
+ my ($fname, $contents) = @_;
+ my $fn = $node->basedir . '/' . $fname;
+ #ok(not -e $fn, "$fn must not already exists");
+ append_to_file($fn, $contents);
+ return $fn;
+}
+
+# invoke psql
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected exit status
+# - in: input stream
+# - out: list of re to check on stdout
+# - err: list of re to check on stderr
+# - name: of the test
+# - more: more raw arguments
+sub psql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $in, $out, $err, $name, @more) = @_;
+ $opts =~ s/^!// or push @more, '-X';
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+ $node->command_checks_all(\@cmd, $stat, $in, $out, $err, $name);
+ return;
+}
+
+# invoke psql interactively, making it believe it is connected to a tty
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected final exit status
+# - timeout: for interactions, in second
+# - inout: list of input expected re
+# - name: of the test
+# - remove: tell to remove init/start/end checks
+sub ipsql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $timeout, $inout, $name, $remove) = @_;
+
+ # build command to run
+ my @more = ();
+ @more = ('-X') unless $opts =~ /^!/;
+ $opts =~ s/^!//;
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+
+ # check it is running at start and end, and exit cleanly
+ unless ($remove)
+ {
+ unshift @$inout, [ "\\echo STARTING :VERSION_NUM\n", qr/STARTING \d+.*postgres=\# /s ];
+ push @$inout, [ "\\echo ENDING :VERSION_NUM\\q\n", qr/ENDING \d+$/s ];
+ }
+
+ # call the stuff
+ $node->icommand_checks(
+ \@cmd, $stat, $timeout,
+ $remove ? undef : qr/psql .*Type "help" for help..*postgres=# /s,
+ $inout, $name);
+ return;
+}
+
+my $EMPTY = [ qr/^$/ ];
+
+#
+# check various options
+#
+# this cannot be tested from within a SQL script
+psql('!--help', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql --help');
+psql('!-?', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql -?');
+psql('!--version', 0, '',
+ [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql --version');
+psql('--help=variables', 0, '',
+ [ qr{VERSION_NAME}, qr{unicode_border_linestyle}, qr{PSQL_EDITOR} ],
+ $EMPTY, 'psql --help=variables');
+# look for plenty of backslash-commands
+psql('--help=commands', 0, '',
+ [ qr{\\copyright}, qr{\\pset}, qr{(?!VERSION_NAME)} ],
+ $EMPTY, 'psql --help=commands');
+
+# tested elsewhere: -a -A -d -e -h -L
+psql('-b', 0, "SELECT 1/0;\n",
+ $EMPTY, [ qr{STATEMENT: SELECT 1/0}, qr{ERROR: division by zero} ], 'psql -b');
+psql('-c \\q', 0, '', $EMPTY, $EMPTY, 'psql -c');
+psql('-c \\l -c \\l', 0, '', [ qr{template0.*template0}s ], $EMPTY, 'psql -c -c');
+psql('', 0, '', [ qr{(?!1234)}, qr{5678}s ], $EMPTY, 'psql -c ;;', ('-c', 'SELECT 1234; SELECT 5678;'));
+psql('-E', 0, "\\l\n", [ qr{\* QUERY \*}, qr{template0} ], $EMPTY, 'psql -E');
+psql('-A -F _', 0, "SELECT 54, 32;\n", [ qr{54_32} ], $EMPTY, 'psql -F _');
+psql('-H', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -H');
+psql('-l', 0, '', [ qr{template0}, qr{template1} ], $EMPTY, 'psql -l');
+psql('-n', 0, "\\q\n", $EMPTY, $EMPTY, 'psql -n');
+psql('-P format=html', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -P format=html');
+psql('-P tuples_only', 0, "\\pset\n", [ qr{tuples_only.*on} ], $EMPTY, 'psql -P tuples_only');
+psql('-A -R_', 0, "VALUES (1), (2);\n", [ qr{_1_2_} ], $EMPTY, 'psql -R _');
+psql('-s', 0, "SELECT 1;\n", [ qr{verify command}, qr{SELECT 1}, qr{press return to proceed} ], $EMPTY, 'psql -s');
+psql('-S', 0, "SELECT 1 AS one\nSELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], $EMPTY, 'psql -S');
+psql('-H -T width=100', 0, "SELECT 1 AS one;\n", [ qr{table.*width=100} ], $EMPTY, 'psql -T ...');
+psql('-X -V', 0, '', [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql -X -V');
+psql('', 0, "\\echo :VERSION_NUM\n", [ qr{\d{6}} ], $EMPTY, 'psql -- show VERSION_NUM');
+psql('-v VERSION_NUM', 0, "\\echo :VERSION_NUM\n", [ qr{:VERSION_NUM} ], $EMPTY, 'psql -v ...');
+psql('-v VERSION_NUM=bla', 0, "\\echo :VERSION_NUM\n", [ qr{bla} ], $EMPTY, 'psql -v ...=.');
+psql('-w', 0, '', $EMPTY, $EMPTY, 'psql -w');
+# nope, interacts on tty
+#psql('-W', 0, "foo\n", [ qr{^$} ], [ qr{^$} ], 'psql -W');
+psql('-x', 0, "SELECT 1 AS one, 2 AS two;\n", [ qr{one \| 1.*two \| 2}s ], $EMPTY, 'psql -x');
+# some issue, \0 is not welcome somewhere
+#psql('-A -z', "SELECT 1 AS one, 2 AS two;\n", [ qr{one.two}s, qr{1.2}s ], [ qr{^$} ], 'psql -z');
+#psql('-A -0', "SELECT 1 AS one, 2 AS two;\n", [ qr{two.1}s ], [ qr{^$} ], 'psql -0');
+psql('-1', 0, "SELECT 54;\nSELECT 32;\n", [ qr{54}, qr{32} ], $EMPTY, 'psql -1');
+psql('-X -?', 0, '',
+ [ qr{interactive terminal}, qr{Usage:}, qr{Connection options:} ], $EMPTY, 'psql -X -?');
+psql('--csv', 0, "SELECT 1 AS one, 2 AS two\n", [ qr{one,two}, qr{1,2} ], $EMPTY, 'psql --csv');
+
+#
+# create some objects to help \d tests later
+#
+psql('', 0, '-- create some objects
+CREATE USER regress_psql_tap_1;
+ALTER ROLE regress_psql_tap_1 SET enable_indexscan TO off;
+CREATE TABLE regress_psql_tap_1_t1(data TEXT NOT NULL);
+CREATE FUNCTION regress_psql_tap_1_f1() RETURNS INTEGER AS $$ SELECT 54321 $$ LANGUAGE SQL;
+CREATE VIEW regress_psql_tap_1_v1 AS SELECT 54321 AS one;
+', [ qr{CREATE ROLE}, qr{ALTER ROLE} ], [ qr{^$} ], 'psql -- create stuff');
+
+#
+# check interactive help
+#
+# this is mostly for coverage, including occasional negatives.
+# we certainly do not want to have an output file with the whole doc.
+my @backslash_help = (
+ # \h command, list of stdout matches
+ [ 'no-help', [ qr{No help available for} ] ],
+ [ '' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ [ ' ' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ # lists
+ [ 'ALTER', [ qr{AGGREGATE}, qr{ROLE}, qr{VIEW}, qr{(?!ACCESS METHOD)}, qr{(?!TRANSFORM)} ] ],
+ [ 'ALTER TEXT SEARCH', [ qr{CONFIGURATION}, qr{DICTIONARY}, qr{PARSER}, qr{TEMPLATE} ] ],
+ [ 'ALTER FOREIGN', [ qr{DATA WRAPPER}, qr{TABLE} ] ],
+ [ 'CREATE', [ qr{ACCESS METHOD}, qr{TRANSFORM} ] ],
+ [ 'DROP', [ qr{ACCESS METHOD}, qr{VIEW} ] ],
+ # details
+ [ 'ABORT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'ALTER AGGREGATE', [ qr{RENAME TO}, qr{(?!CONVERSION)} ] ],
+ [ 'ALTER CONVERSION', [ qr{SET SCHEMA}, qr{(?!AGGREGATE)} ] ],
+ [ 'ALTER COLLATION', [ qr{REFRESH VERSION} ] ],
+ [ 'ALTER DATABASE', [ qr{CONNECTION LIMIT} ] ],
+ [ 'ALTER DEFAULT PRIVILEGES', [ qr{GRANT} ] ],
+ [ 'ALTER DOMAIN', [ qr{VALIDATE CONSTRAINT} ] ],
+ [ 'ALTER EVENT TRIGGER', [ qr{DISABLE} ] ],
+ [ 'ALTER EXTENSION', [ qr{OPERATOR FAMILY} ] ],
+ [ 'ALTER FOREIGN DATA WRAPPER', [ qr{VALIDATOR} ] ],
+ [ 'ALTER FOREIGN TABLE', [ qr{SET STORAGE} ] ],
+ [ 'ALTER FUNCTION', [ qr{PARALLEL} ] ],
+ [ 'ALTER GROUP', [ qr{DROP USER} ] ],
+ [ 'ALTER INDEX', [ qr{ALL IN TABLESPACE} ] ],
+ [ 'ALTER LANGUAGE', [ qr{PROCEDURAL}, qr{(?!INDEX)} ] ],
+ [ 'ALTER LARGE OBJECT', [ qr{OWNER TO} ] ],
+ [ 'ALTER MATERIALIZED VIEW', [ qr{SET TABLESPACE} ] ],
+ [ 'ALTER OPERATOR', [ qr{JOIN =} ] ],
+ [ 'ALTER OPERATOR CLASS', [ qr{SET SCHEMA}, qr{(?!LANGUAGE)} ] ],
+ [ 'ALTER OPERATOR FAMILY', [ qr{FUNCTION} ] ],
+ [ 'ALTER POLICY', [ qr{WITH CHECK} ] ],
+ [ 'ALTER PROCEDURE', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER PUBLICATION', [ qr{DROP TABLE} ] ],
+ [ 'ALTER ROLE', [ qr{BYPASSRLS} ] ],
+ [ 'ALTER ROUTINE', [ qr{LEAKPROOF} ] ],
+ [ 'ALTER RULE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER SCHEMA', [ qr{OWNER TO} ] ],
+ [ 'ALTER SEQUENCE', [ qr{NO MAXVALUE} ] ],
+ [ 'ALTER SERVER', [ qr{VERSION} ] ],
+ [ 'ALTER STATISTICS', [ qr{SET SCHEMA} ] ],
+ [ 'ALTER SUBSCRIPTION', [ qr{REFRESH PUBLICATION} ] ],
+ [ 'ALTER SYSTEM', [ qr{RESET ALL} ] ],
+ [ 'ALTER TABLE', [ qr{ALL IN TABLESPACE}, qr{DISABLE ROW LEVEL SECURITY} ] ],
+ [ 'ALTER TABLESPACE', [ qr{RESET} ] ],
+ [ 'ALTER TEXT SEARCH CONFIGURATION', [ qr{ALTER MAPPING FOR} ] ],
+ [ 'ALTER TEXT SEARCH DICTIONARY', [ qr{OWNER TO} ] ],
+ [ 'ALTER TEXT SEARCH PARSER', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TEXT SEARCH TEMPLATE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TRIGGER', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER TYPE', [ qr{RENAME ATTRIBUTE} ] ],
+ [ 'ALTER USER', [ qr{NOREPLICATION}, qr{(?!MAPPING)} ] ],
+ [ 'ALTER USER MAPPING', [ qr{SERVER} ] ],
+ [ 'ALTER VIEW', [ qr{DROP DEFAULT} ] ],
+ [ 'ANALYZE', [ qr{VERBOSE}, qr{SKIP_LOCKED} ] ],
+ [ 'BEGIN', [ qr{ISOLATION LEVEL} ] ],
+ [ 'CALL', [] ],
+ [ 'CHECKPOINT', [] ],
+ [ 'CLOSE', [ qr{ALL} ] ],
+ [ 'CLUSTER', [ qr{VERBOSE} ] ],
+ [ 'COMMENT', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'COMMIT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'COMMIT PREPARED', [] ],
+ [ 'COPY', [ qr{FORMAT}, qr{FORCE_NOT_NULL} ] ],
+ [ 'CREATE ACCESS METHOD', [ qr{HANDLER} ] ],
+ [ 'CREATE AGGREGATE', [ qr{SSPACE}, qr{SORTOP}, qr{FINALFUNC} ] ],
+ [ 'CREATE CAST', [ qr{AS IMPLICIT} ] ],
+ [ 'CREATE COLLATION', [ qr{LC_CTYPE}, qr{DETERMINISTIC} ] ],
+ [ 'CREATE CONVERSION', [ qr{FOR} ] ],
+ [ 'CREATE DATABASE', [ qr{ALLOW_CONNECTIONS} ] ],
+ [ 'CREATE DOMAIN', [ qr{COLLATE} ] ],
+ [ 'CREATE EVENT TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE EXTENSION', [ qr{FROM} ] ],
+ [ 'CREATE FOREIGN DATA WRAPPER', [ qr{NO VALIDATOR} ] ],
+ [ 'CREATE FOREIGN TABLE', [ qr{SERVER} ] ],
+ [ 'CREATE FUNCTION', [ qr{CALLED ON NULL INPUT}, qr{(?!PROCEDURE)} ] ],
+ [ 'CREATE GROUP', [ qr{ADMIN} ] ],
+ [ 'CREATE INDEX', [ qr{CONCURRENTLY}, qr{WHERE} ] ],
+ [ 'CREATE LANGUAGE', [ qr{TRUSTED} ] ],
+ [ 'CREATE MATERIALIZED VIEW', [ qr{DATA} ] ],
+ [ 'CREATE OPERATOR', [ qr{LEFTARG} ] ],
+ [ 'CREATE OPERATOR CLASS', [ qr{FOR ORDER BY} ] ],
+ [ 'CREATE OPERATOR FAMILY', [ qr{USING} ] ],
+ [ 'CREATE POLICY', [ qr{PERMISSIVE} ] ],
+ [ 'CREATE PROCEDURE', [ qr{SECURITY INVOKER}, qr{(?!FUNCTION)} ] ],
+ [ 'CREATE PUBLICATION', [ qr{FOR ALL TABLES} ] ],
+ [ 'CREATE ROLE', [ qr{SYSID} ] ],
+ [ 'CREATE RULE', [ qr{ALSO} ] ],
+ [ 'CREATE SCHEMA', [ qr{AUTHORIZATION} ] ],
+ [ 'CREATE SEQUENCE', [ qr{NO MINVALUE} ] ],
+ [ 'CREATE SERVER', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'CREATE STATISTICS', [ qr{FROM} ] ],
+ [ 'CREATE SUBSCRIPTION', [ qr{CONNECTION} ] ],
+ [ 'CREATE TABLE', [ qr{PARTITION BY} ] ],
+ [ 'CREATE TABLE AS', [ qr{ON COMMIT} ] ],
+ [ 'CREATE TABLESPACE', [ qr{LOCATION} ] ],
+ [ 'CREATE TEXT SEARCH CONFIGURATION', [ qr{PARSER} ] ],
+ [ 'CREATE TEXT SEARCH DICTIONARY', [ qr{TEMPLATE} ] ],
+ [ 'CREATE TEXT SEARCH PARSER', [ qr{GETTOKEN} ] ],
+ [ 'CREATE TEXT SEARCH TEMPLATE', [ qr{LEXIZE} ] ],
+ [ 'CREATE TRANSFORM', [ qr{FROM SQL WITH FUNCTION} ] ],
+ [ 'CREATE TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE TYPE', [ qr{SUBTYPE} ] ],
+ [ 'CREATE USER', [ qr{PASSWORD NULL} ] ],
+ [ 'CREATE USER MAPPING', [ qr{SERVER} ] ],
+ [ 'CREATE VIEW', [ qr{CASCADED} ] ],
+ [ 'DEALLOCATE', [ qr{PREPARE} ] ],
+ [ 'DECLARE', [ qr{INSENSITIVE} ] ],
+ [ 'DELETE', [ qr{WHERE CURRENT OF} ] ],
+ [ 'DISCARD', [ qr{PLANS} ] ],
+ [ 'DO', [ qr{LANGUAGE} ] ],
+ [ 'DROP ACCESS METHOD', [ qr{CASCADE} ] ],
+ [ 'DROP AGGREGATE', [ qr{ORDER BY} ] ],
+ [ 'DROP CAST', [ qr{RESTRICT} ] ],
+ [ 'DROP COLLATION', [ qr{RESTRICT} ] ],
+ [ 'DROP CONVERSION', [ qr{RESTRICT} ] ],
+ [ 'DROP DATABASE', [ qr{IF EXISTS} ] ],
+ [ 'DROP DOMAIN', [ qr{RESTRICT} ] ],
+ [ 'DROP EVENT TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP EXTENSION', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN DATA WRAPPER', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP FUNCTION', [ qr{RESTRICT} ] ],
+ [ 'DROP GROUP', [ qr{IF EXISTS} ] ],
+ [ 'DROP INDEX', [ qr{RESTRICT} ] ],
+ [ 'DROP LANGUAGE', [ qr{RESTRICT} ] ],
+ [ 'DROP MATERIALIZED VIEW', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR CLASS', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR FAMILY', [ qr{RESTRICT} ] ],
+ [ 'DROP OWNED', [ qr{CURRENT_USER} ] ],
+ [ 'DROP POLICY', [ qr{RESTRICT} ] ],
+ [ 'DROP PROCEDURE', [ qr{RESTRICT} ] ],
+ [ 'DROP PUBLICATION', [ qr{RESTRICT} ] ],
+ [ 'DROP ROLE', [ qr{IF EXISTS} ] ],
+ [ 'DROP ROUTINE', [ qr{RESTRICT} ] ],
+ [ 'DROP RULE', [ qr{RESTRICT} ] ],
+ [ 'DROP SCHEMA', [ qr{RESTRICT} ] ],
+ [ 'DROP SEQUENCE', [ qr{RESTRICT} ] ],
+ [ 'DROP SERVER', [ qr{RESTRICT} ] ],
+ [ 'DROP STATISTICS', [ qr{IF EXISTS} ] ],
+ [ 'DROP SUBSCRIPTION', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLESPACE', [ qr{IF EXISTS} ] ],
+ [ 'DROP TEXT SEARCH CONFIGURATION', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH DICTIONARY', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH PARSER', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH TEMPLATE', [ qr{RESTRICT} ] ],
+ [ 'DROP TRANSFORM', [ qr{LANGUAGE} ] ],
+ [ 'DROP TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP TYPE', [ qr{RESTRICT} ] ],
+ [ 'DROP USER', [ qr{IF EXISTS} ] ],
+ [ 'DROP USER MAPPING', [ qr{SERVER} ] ],
+ [ 'DROP VIEW', [ qr{RESTRICT} ] ],
+ [ 'END', [ qr{TRANSACTION}, qr{CHAIN}, qr{(?!COMMIT)} ] ],
+ [ 'EXECUTE', [ ] ],
+ [ 'EXPLAIN', [ qr{VERBOSE} ] ],
+ [ 'FETCH', [ qr{RELATIVE} ] ],
+ [ 'GRANT', [ qr{ALL TABLES} ] ],
+ [ 'IMPORT FOREIGN SCHEMA', [ qr{FROM SERVER} ] ],
+ [ 'INSERT', [ qr{ON CONFLICT} ] ],
+ [ 'LISTEN', [ ] ],
+ [ 'LOAD', [ ] ],
+ [ 'LOCK', [ qr{NOWAIT} ] ],
+ [ 'MOVE', [ qr{FORWARD} ] ],
+ [ 'NOTIFY', [ ] ],
+ [ 'PREPARE', [ qr{AS} ] ],
+ [ 'PREPARE TRANSACTION', [ ] ],
+ [ 'REASSIGN OWNED', [ qr{SESSION_USER} ] ],
+ [ 'REFRESH MATERIALIZED VIEW', [ qr{CONCURRENTLY} ] ],
+ [ 'REINDEX', [ qr{DATABASE} ] ],
+ [ 'RELEASE', [ qr{SAVEPOINT} ] ],
+ [ 'RESET', [ qr{RESET ALL} ] ],
+ [ 'REVOKE', [ qr{PRIVILEGES} ] ],
+ [ 'ROLLBACK', [ qr{CHAIN} ] ],
+ [ 'ROLLBACK PREPARED', [ ]],
+ [ 'SAVEPOINT', [ ] ],
+ [ 'SECURITY LABEL', [ qr{SUBSCRIPTION} ] ],
+ [ 'SELECT', [ qr{RECURSIVE} ] ],
+ [ 'SET', [ qr{SESSION} ] ],
+ [ 'SET CONSTRAINTS', [ qr{IMMEDIATE} ] ],
+ [ 'SET ROLE', [ qr{ROLE NONE} ] ],
+ [ 'SET SESSION AUTHORIZATION', [ qr{DEFAULT} ] ],
+ [ 'SET TRANSACTION', [ qr{SNAPSHOT} ] ],
+ [ 'SHOW', [ qr{SHOW ALL} ] ],
+ [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ],
+ [ 'TABLE', [ qr{ONLY} ] ], # hmmm...
+ [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ],
+ [ 'UNLISTEN', [ ] ],
+ [ 'UPDATE', [ qr{RETURNING} ] ],
+ [ 'VACUUM', [ qr{FREEZE} ] ],
+ [ 'VALUES', [ qr{ORDER BY} ] ],
+ [ 'WITH', [ qr{RECURSIVE} ] ], # SELECT duplicate?
+);
+
+for my $h (@backslash_help)
+{
+ my ($cmd, $out) = @$h;
+ push @$out, qr{$cmd};
+ psql('', 0, "\\h $cmd\n", $out, [ qr{^$} ], "psql -- \\h $cmd");
+}
+
+# special cases
+psql('', 0, "\\h ROLLBACK TO SAVEPOINT\n", [ qr{ROLLBACK}, qr{SAVEPOINT} ],
+ [ qr{^$} ], "psql -- \\h ROLLBACK TO SAVEPOINT");
+psql('', 0, "\\h SELECT INTO\n", [ qr{SELECT}, qr{INTO} ],
+ [ qr{^$} ], "psql -- \\h SELECT INTO");
+
+#
+# check describe and other backslash commands
+#
+# the output can vary significantly, especially with +
+my @backslash_out = (
+ # empty
+ [ "\\d\n", [ qr{List of relations}, qr{regress_psql_tap_1_t1}, qr{(?!pg_locks)} ] ],
+ [ "\\dS\n", [ qr{List of relations}, qr{pg_locks} ] ],
+ # aggregates
+ [ "\\da\n", [ qr{List of aggregate functions}, qr{(?!Description)} ] ],
+ [ "\\da+\n", [ qr{List of aggregate functions}, qr{(Description)} ] ],
+ [ "\\daS\n", [ qr{array_agg}, qr{avg}, qr{bit_and}, qr{bit_or}, qr{count}, qr{max} ] ],
+ # access methods
+ [ "\\dA\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{(?!Description)} ] ],
+ [ "\\dA+\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{Description} ] ],
+ # tablespaces
+ [ "\\db\n", [ qr{List of tablespaces}, qr{pg_default}, qr{pg_global}, qr{(?!Size)} ] ],
+ [ "\\db+ pg_def*\n", [ qr{List of tablespaces}, qr{pg_default}, qr{(?!pg_global)}, qr{Size} ] ],
+ # functions
+ [ "\\df\n", [ qr{List of functions} ] ],
+ [ "\\df+\n", [ qr{List of functions} ] ],
+ [ "\\dftS\n", [ qr{RI_}, qr{Type}, qr{(?!Volatility)}, qr{(?!Parallel)} ] ],
+ [ "\\dftS+\n", [ qr{Volatility}, qr{Parallel} ] ],
+ [ "\\dfwS\n", [ qr{lag}, qr{dense_rank}, qr{(?!Owner)}, qr{(?!Language)} ] ],
+ [ "\\dfwS+\n", [ qr{cume_dist}, qr{Owner}, qr{Language} ] ],
+ [ "\\dfnS\n", [ qr{abbrev}, qr{(?!bit_or)} ] ],
+ [ "\\dfaS\n", [ qr{bit_and}, qr{(?!abs)} ] ],
+ [ "\\dfpS\n", [ qr{List of functions} ] ],
+ [ "\\dfwtS\n", [ qr{RI_FKey_}, qr{last_value}, qr{nth_value}, qr{(?!bit_and)} ] ],
+ # type
+ [ "\\dTS charac*\n", [ qr{List of data types}, qr{character varying}, qr{(?!double)}, qr{(?!Size)} ] ],
+ [ "\\dTS+\n", [ qr{aclitem}, qr{Size} ] ],
+ # operator
+ [ "\\doS\n", [ qr{List of operators}, qr{!~\*} ] ],
+ [ "\\doS+\n", [ qr{<->}, qr{Result type}] ],
+ # databases
+ [ "\\l\n", [ qr{List of databases}, qr{template0}, qr{template1}, qr{(?!Size)} ] ],
+ [ "\\l a*\n", [ qr{(?!template[01])} ] ],
+ [ "\\l+\n", [ qr{Size}, qr{Encoding} ] ],
+ [ "\\list\n", [ qr{List of databases} ] ],
+ # permissions: well tested
+ # default acls (empty)
+ [ "\\ddp\n", [ qr{Default access privileges} ] ],
+ # descriptions (empty)
+ [ "\\dd\n", [ qr{Object descriptions} ] ],
+ # tables: well tested
+ # roles/users/groups
+ [ "\\du\n", [ qr{Superuser}, qr{regress_psql_tap_1}, qr{List of roles}, qr{(?!Description)} ] ],
+ [ "\\du+\n", [ qr{Superuser}, qr{List of roles}, qr{Description} ] ],
+ [ "\\dg\n", [ qr{Superuser}, qr{List of roles} ] ],
+ # role settings
+ [ "\\drds\n", [ qr{List of settings} ] ],
+ [ "\\drds regress_* *\n", [ qr{List of settings} ] ],
+ # index/...
+ [ "\\diS\n", [ qr{List of relations}, qr{pg_am_oid_index} ] ],
+ [ "\\diS+\n", [ qr{Table} ] ],
+ # partition tables: well tested
+ # large objects
+ [ "\\dl", [ qr{Large objects} ] ],
+ # languages
+ [ "\\dL\n", [ qr{List of languages}, qr{plpgsql}, qr{(?!Trusted)} ] ],
+ [ "\\dL+\n", [ qr{plpgsql}, qr{Trusted} ] ],
+ # domains
+ [ "\\dD\n", [ qr{List of domains}, qr{(?!Access privileges)} ] ],
+ [ "\\dD+\n", [ qr{List of domains}, qr{Access privileges} ] ],
+ # conversions
+ [ "\\dcS\n", [ qr{List of conversions}, qr{UTF8}, qr{(?!Description)} ] ],
+ [ "\\dcS+\n", [ qr{List of conversions}, qr{LATIN1}, qr{Description} ] ],
+ # event triggers (empty)
+ [ "\\dy\n", [ qr{List of event triggers}, qr{(?!Description)} ] ],
+ [ "\\dy+\n", [ qr{List of event triggers}, qr{Description} ] ],
+ # casts
+ [ "\\dC\n", [ qr{List of casts}, qr{timestamp without time zone}, qr{(?!Description)} ] ],
+ [ "\\dC+\n", [ qr{Function}, qr{Implicit}, qr{Description} ] ],
+ # collations
+ [ "\\dOS\n", [ qr{List of collations}, qr{Deterministic}, qr{(?!Description)} ] ],
+ [ "\\dOS+\n", [ qr{POSIX}, qr{Description} ] ],
+ # schemas
+ [ "\\dn\n", [ qr{List of schemas}, qr{Owner}, qr{(?!Description)} ] ],
+ [ "\\dn+\n", [ qr{Access privileges}, qr{Description} ] ],
+ # text search misc.
+ [ "\\dFp\n", [ qr{List of text search parsers}, qr{default} ] ],
+ [ "\\dFp+\n", [ qr{hword}, qr{numword} ] ],
+ [ "\\dFd\n", [ qr{List of text search dictionaries}, qr{simple} ] ],
+ [ "\\dFd+\n", [ qr{Init options}, qr{Description} ] ],
+ [ "\\dFt\n", [ qr{List of text search templates}, qr{simple} ] ],
+ [ "\\dFt+\n", [ qr{Lexize}, qr{Description} ] ],
+ [ "\\dF\n", [ qr{List of text search configurations}, qr{simple} ] ],
+ [ "\\dF+\n", [ qr{email}, qr{url_path} ] ],
+ # extensions
+ [ "\\dx\n", [ qr{List of installed extensions}, qr{Version}, qr{plpgsql} ] ],
+ [ "\\dx+\n", [ qr{Objects in extension}, qr{plpgsql_call_handler} ] ],
+ # other backslash commands
+ [ "\\f\n", [ qr{Field separator is} ] ],
+ [ "\\H\\H\n", [ qr{Output format is html.* is aligned}s ] ],
+ [ "\\\?\n", [ qr{General}, qr{Help}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\C foobar\n", [ qr{Title is "foobar"} ] ],
+ [ "\\cd /\n\\cd\n", [ qr{^$} ] ],
+ [ "\\conninfo\n", [ qr{You are connected to database} ] ],
+ [ "\\copy (SELECT 5432 UNION SELECT 2345 ORDER BY 1) TO STDOUT\n", [ qr/\b2345\b.*\b5432\b/s ] ],
+ [ "\\copyright\n", [ qr/The Regents of the University of California/ ] ],
+ [ "\\encoding\n", [ qr/./ ] ],
+ [ "\\encoding :ENCODING\n", $EMPTY ],
+ [ "\\errverbose\n", [ qr{There is no previous error} ] ],
+ [ "\\lo_list\n", [ qr{Large objects} ] ],
+ [ "\\if true\\q\\endif\n", $EMPTY ],
+ # ???
+ #[ "SELECT md5('hello world');\n\\s\n", [ qr{5eb63bbbe0}, qr{SELECT md5} ] ],
+ [ "\\set\n", [ qr{ENCODING = }, qr{VERSION_NUM = } ] ],
+ [ "\\set COMP_KEYWORD_CASE preserve-lower\n\\set COMP_KEYWORD_CASE lower\n" .
+ "\\set COMP_KEYWORD_CASE upper\n\\echo :COMP_KEYWORD_CASE", [ qr/upper/ ] ],
+ [ "\\set HISTCONTROL ignorespace\n\\set HISTCONTROL ignoredups\n" .
+ "\\set HISTCONTROL ignoreboth\n\\echo :HISTCONTROL", [ qr/ignoreboth/ ] ],
+ [ "\\set ECHO_HIDDEN on\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/template0/ ] ],
+ [ "\\set ECHO_HIDDEN noexec\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/(?!template0)/ ] ],
+ [ "\\set LAST_ERROR_MESSAGE foo bla\n\\echo :LAST_ERROR_MESSAGE\n", [ qr{foobla} ] ],
+ [ "\\set AUTOCOMMIT off\n\\set ON_ERROR_ROLLBACK on\nSELECT 5432 AS pg;\nSELECT 2345 AS pg;\n",
+ [ qr{5432}, qr{2345} ] ],
+ [ "\\set ON_ERROR_ROLLBACK interactive\n\\echo :ON_ERROR_ROLLBACK\n", [ qr{interactive} ] ],
+ [ "\\set VERBOSITY verbose\n\\echo :VERBOSITY\n", [ qr{verbose} ] ],
+ [ "\\set foo bla\n\\echo :foo\\unset foo\\echo :foo\n", [ qr{^bla.*:foo}s ] ],
+ [ "\\set foo bla\n\\echo :'foo' :\"foo\"\n", [ qr{'bla'}, qr{"bla"} ] ],
+ [ "\\setenv FOO bla\n\\setenv FOO\n", [ qr{^$} ] ],
+ [ "\\timing\nSELECT 1;\n\\timing\n", [ qr{Timing is on.*Timing is off}s, qr(Time: \d+\.\d{3} ms) ] ],
+ [ "\\timing ON\n\\timing OFF\n", [ qr{Timing is on.*Timing is off}s ] ],
+ [ "\\T foo\n", [ qr{Table attributes are "foo"} ] ],
+ [ "\\T\n", [ qr{Table attributes unset} ] ],
+ # help variants
+ [ "\\?\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? commands\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? bad\n", [ qr{General} ] ],
+ [ "\\? variables\n",
+ [ qr{psql variables:}, qr{Display settings:}, qr{Environment variables} ] ],
+ [ "\\? options\n",
+ [ qr{interactive terminal}, qr{Report bugs} ] ],
+ # pset format
+ [ "\\pset format aligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is aligned}, qr{ 1 \| 2} ] ],
+ [ "\\pset format asciidoc\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is asciidoc}, qr{\|1 \|2} ] ],
+ [ "\\pset format csv\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is csv}, qr{1,2} ] ],
+ [ "\\pset format html\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is html}, qr{<td align="right">1</td>} ] ],
+ [ "\\pset format latex\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex}, qr{1 \& 2} ] ],
+ [ "\\pset format latex-longtable\n\\pset format\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex-longtable}, qr{\\raggedright\{1\}} ] ],
+ [ "\\pset format troff-ms\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is troff-ms}, qr{1\t2} ] ],
+ [ "\\pset format unaligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is unaligned}, qr{1\|2} ] ],
+ [ "\\pset format wrapped\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is wrapped}, qr{ 1 \| 2} ] ],
+ [ "\\pset format\n", [ qr{Output format is aligned} ] ],
+ # pset linestyle
+ [ "\\pset linestyle ascii\n", [ qr{Line style is ascii} ] ],
+ [ "\\pset linestyle old-ascii\n", [ qr{Line style is old-ascii} ] ],
+ [ "\\pset linestyle unicode\n", [ qr{Line style is unicode} ] ],
+ # pset unicode_*_linestyle
+ [ "\\pset unicode_header_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_header_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_border_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_border_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_column_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_column_linestyle double\n", [ qr{"double"} ] ],
+);
+
+for my $h (@backslash_out)
+{
+ my ($in, $out) = @$h;
+ psql('', 0, $in, $out, [ qr{^$} ], "psql -- $in");
+}
+
+# test some errors
+my @backslash_err = (
+ [ "\\unknown stuff\n", [ qr/invalid command / ] ],
+ [ "\\cd /no/such/dir\n", [ qr/could not change directory to/ ] ],
+ [ "\\copy\n", [ qr/arguments required/ ] ],
+ [ "\\copy bad stuff on the line\n", [ qr/parse error at \"/ ] ],
+ [ "\\copy binary (\n", [ qr/parse error at end of line/ ] ],
+ [ "\\copy public.regress_psql_tap_1_t1(data) to '/no/such/file'", [ qr{/no/such/file} ] ],
+ [ "\\encoding no-such-encoding\n", [ qr/invalid encoding name/ ] ],
+ [ "BAD;\n\\errverbose\n", [ qr/syntax error at or near "BAD"/, qr/\b42601\b/ ] ],
+ [ "\\dfX\n", [ qr/invalid command/ ] ],
+ [ "\\dPX\n", [ qr/invalid command/ ] ],
+ [ "\\drX\n", [ qr/invalid command/ ] ],
+ [ "\\dRX\n", [ qr/invalid command/ ] ],
+ [ "\\dFX\n", [ qr/invalid command/ ] ],
+ [ "\\deX\n", [ qr/invalid command/ ] ],
+ [ "\\dX\n", [ qr/invalid command/ ] ],
+ [ "\\i\n", [ qr/missing required argument/ ] ],
+ [ "\\ir\n", [ qr/missing required argument/] ],
+ [ "\\lo_bad\n", [ qr/invalid command/ ] ],
+ [ "\\lo_export\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import /no/such/file\n", [ qr{no/such/file} ] ],
+ [ "\\lo_unlink\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_unlink -1\n", [ qr/large object 4294967295 does not exist/ ] ],
+ [ "\\setenv\n", [ qr/missing required argument/ ] ],
+ [ "\\setenv PG_REGRESS_TEST=1\n", [ qr/environment variable name must not contain "="/ ] ],
+ [ "\\unset\n", [ qr/missing required argument/ ] ],
+ [ "\\set foo \`no-such-command\`\n", [ qr/no-such-command/ ] ],
+ [ "\\set === xxx\n", [ qr/invalid variable name/ ] ],
+ [ "\\w\n", [ qr/missing required argument/ ] ],
+ [ "\\pset format bad\n", [ qr/allowed formats are aligned, asciidoc/ ] ],
+ [ "\\pset linestyle bad\n", [ qr/allowed line styles are ascii, old-ascii, unicode/ ] ],
+ [ "\\pset unicode_border_linestyle bad\n", [ qr/allowed Unicode border line styles are single, double/ ] ],
+ [ "\\pset unicode_column_linestyle bad\n", [ qr/allowed Unicode column line styles are single, double/ ] ],
+ [ "\\pset unicode_header_linestyle bad\n", [ qr/allowed Unicode header line styles are single, double/ ] ],
+ [ "COPY regress_psql_tap_1_t1 FROM STDIN \\watch 0.01\n", [ qr/watch cannot be used with COPY/ ] ],
+ [ "\\watch 0.01\n", [ qr/watch cannot be used with an empty query/ ] ],
+ # check variable constraints values
+ [ "\\set ECHO bad\n", [ qr{none, errors, queries, all} ] ],
+ [ "\\set ECHO_HIDDEN bad\n", [ qr{on, off, noexec} ] ],
+ [ "\\set ON_ERROR_ROLLBACK bad\n", [ qr{on, off, interactive} ] ],
+ [ "\\set COMP_KEYWORD_CASE bad\n", [ qr{lower, upper, preserve-lower, preserve-upper} ] ],
+ [ "\\set HISTCONTROL bad\n", [ qr{none, ignorespace, ignoredups, ignoreboth} ] ],
+ [ "\\set VERBOSITY bad\n", [ qr{default, verbose, terse, sqlstate} ] ],
+ [ "\\set SHOW_CONTEXT bad\n", [ qr{never, errors, always} ] ],
+);
+
+for my $h (@backslash_err)
+{
+ my ($in, $err) = @$h;
+ psql('-L /dev/null', 0, $in, [ qr{^$} ], $err, "psql -- $in");
+}
+
+# other errors
+
+# \timing
+psql('', 0, "\\timing bad\n",
+ [ qr{Timing is off} ], [ qr{unrecognized value} ], 'psql \timing error');
+
+# check stdout vs stderr output
+psql('', 0, "\\echo hello\n\\warn world\n\\q\n",
+ [ qr{^hello$} ], [ qr{^world$} ], 'psql in/out/err');
+
+# \watch test
+psql('', 0,
+ "CREATE TEMP SEQUENCE tmp_seq MAXVALUE 3 NO CYCLE;\n" .
+ "\\t on\n" .
+ "\\timing\n" .
+ "SELECT 'x=' || NEXTVAL('tmp_seq') \\watch 0.01\n",
+ [ qr/x=1\b.*x=2\b.*x=3\b/s, qr/(?!x=[04-9])/ ],
+ [ qr/nextval: reached maximum value of sequence/ ],
+ 'psql watch');
+
+# skip un*x-specific zone if it does not look like un*x
+goto END_UNIX_ZONE
+ unless -e "/dev/null" and -d "/tmp" and -x "/bin/cat" and -x "/bin/echo";
+
+# this probably only works under UN*X-like systems
+psql('', 0, "\\setenv PSQL_EDITOR echo\nSELECT 1 AS one;\n\\e\n\\p\n",
+ [ qr/one.*one/s, qr/SELECT 1/ ], $EMPTY, 'psql edit');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ef regress_psql_tap_1_f1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ef');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ev regress_psql_tap_1_v1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ev');
+psql('', 0, "\\setenv FOO bla\\set foo \`echo \$FOO\`\n\\echo :foo\n",
+ [ qr/bla/ ], $EMPTY, 'psql backtick');
+psql('', 0, "\\setenv FOO blabla\n\\! echo \$FOO\n",
+ [ qr/blabla/ ], $EMPTY, 'psql !');
+
+psql('-o /dev/null', 0, "SELECT 5432 AS pg\n", $EMPTY, $EMPTY, 'psql -o null');
+psql('', 0, "\\o /dev/null\nSELECT 1;\n", $EMPTY, $EMPTY, 'psql \o null');
+psql('', 0, "\\o | cat\nSELECT 5432 AS pg;\n\\o\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql \o cat');
+
+psql('', 0, "\\s /dev/null\n", $EMPTY, $EMPTY, 'psql \s null');
+
+psql('', 0, "SELECT 5432 AS pg;\n\\w /dev/null\n",
+ [ qr/pg.*\b5432\b/s ], $EMPTY, 'psql \w null');
+
+psql('', 0, "SELECT 1\\g /dev/null\n",
+ $EMPTY, $EMPTY, 'psql \g null');
+psql('', 0, "SELECT 5432\\g | cat\n",
+ [ qr/\b5432\n/ ], $EMPTY, 'psql \g cat');
+
+psql('--log-file=/dev/null', 0, "SELECT 5432 AS pg\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql -L null');
+
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) FROM PROGRAM 'echo moe'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy echo');
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) TO PROGRAM 'cat'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy cat'); # :-)
+
+END_UNIX_ZONE:
+
+psql('', 0, "\\i /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \i error');
+psql('', 0, "\\i ~/there/is/no/such/file\n",
+ [ qr{^$} ], [ qr{there/is/no/such/file} ], 'psql \i tilde');
+
+# no such file
+psql('', 0, "\\o /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \o error');
+
+psql('', 0, "\\w /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \w error');
+
+psql('-L /no/such/file', 1, '',
+ [ qr{^$} ], [ qr{could not open log file} ], 'psql -L bad');
+
+# \copy errors
+psql('', 0, "\\copy regress_psql_tap_1_t1 to program '/no/such/prgm'",
+ [ qr/COPY 1/ ], [ qr{/no/such/prgm} ], 'psql copy to bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from program '/no/such/prgm'",
+ [ qr/COPY 0/ ], [ qr{/no/such/prgm} ], 'psql copy from bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 to '/tmp'",
+ $EMPTY, [ qr{directory} ], 'psql copy to tmp');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from '/tmp'",
+ $EMPTY, [ qr/directory/ ], 'psql copy to tmp');
+
+# misc errors
+psql('-P format=bad', 1, '', $EMPTY,
+ [ qr/allowed formats are aligned/, qr/could not set printing parameter/ ], 'psql -P bad');
+psql('-Q', 1, '', $EMPTY, [ qr/Try ".* --help" for more information/ ], 'psql -BAD');
+
+#
+# .psqlrc
+#
+# note: reading the system wide psqlrc cannot be avoided
+#
+#import env;
+my $psqlrc = create_test_file('regress_psql_tap_1_psqlrc', "SELECT 1 AS one\n");
+$ENV{PSQLRC} = $psqlrc;
+psql('!', 0, "SELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], [ qr{^$} ], 'psql .psqlrc');
+delete $ENV{PSQLRC};
+ok(unlink $psqlrc, "unlink $psqlrc");
+
+#
+# large objects
+#
+my $lofile = create_test_file('regress_psql_tap_1_lo', '0123456789ABCDEF' x 1024);
+my $esc_lofile = $lofile;
+$esc_lofile =~ s/'/''/g;
+psql('-H', 0,
+ "\\lo_import '$esc_lofile' 'regress psql TAP lo'\n\\lo_list\n",
+ [ qr{lo_import \d+}, qr{Large objects}, qr{regress psql TAP lo} ], [ qr{^$} ], 'psql lo 1');
+ok(unlink $lofile, "unlink $lofile (1)");
+psql('', 0,
+ "SELECT oid AS loboid\n" .
+ "FROM pg_catalog.pg_largeobject_metadata\n" .
+ "WHERE pg_catalog.obj_description(oid, 'pg_largeobject') = 'regress psql TAP lo'\\gset\n" .
+ "\\echo loboid = :loboid\n" .
+ "\\pset tuples_only on\n" .
+ "SELECT 'lo extract: ' || lo_get(:loboid, 826, 3)::TEXT AS extract;\n" .
+ "\\lo_export :loboid '/no/such/dir/stuff'\n" . # this one fails
+ "\\lo_export :loboid '$esc_lofile'\n" .
+ "\\lo_unlink :loboid\n",
+ [ qr{loboid = \d+}, qr{lo extract: \\x414243} ],
+ [ qr{could not open file "/no/such/dir/stuff"} ],
+ 'psql lo 2');
+ok(-e $lofile, "re-created $lofile");
+ok(unlink $lofile, "unlink $lofile (2)");
+# TODO
+#ok(not -e $lofile, "file $lofile is removed");
+
+#
+# INTERACTIVE STUFF
+#
+# pretty slow... espacially the tab-completion thing
+#
+# disable pager to work around pagination interactions
+
+# basic test
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ "\\echo hello world\n", qr/hello world.*postgres=\# /s ],
+ [ "help\n", qr/You are using.*\\copyright.*quit.*postgres=\# /s ],
+ [ "SELECT\nhelp\n\\r\n", qr/help or press control-[CD].*postgres=\# /s ],
+ [ "SELECT\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT (\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT \$\$\nquit\n\$\$\\r\n", qr/Use control-[CD] to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT '\nquit'\\r\n", qr/quit'.*postgres=\# /s ],
+ [ "SELECT '\n\\q\n", qr/Use control-[CD] to quit.*postgres'\# /s ],
+ [ "'\\r\n", qr/buffer reset.*postgres=\# /s ],
+ [ "SELECT 1\n\\p\n\\r\n", qr/SELECT 1.*SELECT 1.*buffer reset.*postgres=\#/s ],
+ #[ "\\prompt tmp_var\nfoobla\n\\echo hello :tmp_var\n\\unset foo\n\\echo world :tmp_var\n",
+ # qr/hello foobla.*world :tmp_var.*postgres=\# /s ],
+ [ "\\password\n", qr/Enter new password: / ],
+ [ "foo\n", qr/Enter it again: / ],
+ [ "bla\n", qr/Passwords didn't match.*postgres=\# /s ],
+ [ "exit\n" ],
+ ],
+ 'ipsql basic', 1);
+
+# signals
+#TODO: reliably check for un*x vs windows
+my $stop = -e '/dev/null' ? "\004" : "\003";
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ $stop, qr/\\q/ ],
+ ],
+ 'ipsql signals 1', 1);
+
+# prompt
+ipsql('-P pager', 0, 5,
+ [ # default prompts
+ [ "SELECT 1 +\n", qr/postgres-\# / ],
+ [ "(2 * \n", qr/postgres\(\# / ],
+ [ "3) + '\n", qr/postgres\'\# / ],
+ [ "4' + \$\$\n", qr/postgres\$\# /],
+ [ "5\$\$ AS \"\n", qr/postgres\"\# / ],
+ [ "seize\" /*\n", qr/postgres\*\# / ],
+ [ " ignored */;\n", qr/\bseize\b.*\b16\b.*postgres=\# /s ],
+ [ "\\if false\n", qr/postgres\@\# / ],
+ [ "\\echo IGNORED\n", qr/command ignored.*if block.*postgres\@\# /s ],
+ # BUG: prompt with - instead of @
+ [ "SELECT 1;\n", qr/query ignored.*if block.*postgres.\# /s ],
+ [ "\\endif\n", qr/postgres=\# / ],
+ [ "\\set SINGLELINE ON\n", qr/postgres\^\# / ],
+ [ "\\set SINGLELINE OFF\n", qr/postgres=\# / ],
+ [ "COPY regress_psql_tap_1_t1(data) FROM STDIN;\n", qr/>> / ],
+ [ "hello\nworld\n\\.\n", qr/COPY 2\b.*postgres=\# /s ],
+ # set new prompts (not tested: %`)
+ [ "\\set PROMPT1 '%%%[%]%?%M%m%>%n%/%~%#%p%R%x%l%:QUIET:%040'\n", qr/%.*\d+(on|off) /s ],
+ [ "\\set PROMPT1 '1> '\n", qr/\b1> / ],
+ [ "\\set PROMPT2 '2> '\n", qr/\b1> / ],
+ [ "\\set PROMPT3 '3> '\n", qr/\b1> / ],
+ [ "COPY regress_psql_tap_1_t1(data)\n", qr/\b2> / ],
+ [ "FROM STDIN;\n", qr/\b3> / ],
+ [ "calvin\nhobbes\nsusie\nmoe\n\\.\n", qr/COPY 4\b.*\b1> /s ],
+ # reset prompts
+ [ "\\set PROMPT1 '%/%R%# '\n", qr/PROMPT1.*postgres=# /s ],
+ [ "\\set PROMPT2 '%/%R%# '\n", qr/PROMPT2.*postgres=# /s ],
+ [ "\\set PROMPT3 '>> '\n", qr/PROMPT3.*postgres=# /s ],
+ ],
+ 'ipsql prompt 1');
+
+# tab-complation
+ipsql('-P pager', 0, 5,
+ [ # commands
+ [ "SEL\t", qr/SELECT / ],
+ [ "* FROM pg_catalog.pg_ca\t", qr/pg_catalog\.pg_cast / ],
+ [ "LIMIT 17;\n", qr/\b17 rows.*postgres=\# /s ],
+ # backslash commands
+ [ "\\d\t", qr/(\\da.*\\dy.*postgres=# )?\\d/s ],
+ [ "y\n", qr/List of event triggers.*postgres=\# /s ],
+ # pset
+ [ "\\pset \t", qr/(unicode_column_linestyle.*postgres=\# )?\\pset/s ],
+ [ "tu\t", qr/tuples_only / ],
+ [ "off\n", qr/postgres=\# / ],
+ [ "\\pset unicode_b\t", qr/unicode_border_linestyle / ],
+ [ "\t", qr/(double.*single.*postgres=\# )?/s ],
+ [ "double\n", qr/Unicode border line style is "double".*postgres=\# /s ],
+ # set
+ [ "\\set VERB\t", qr/VERBOSITY / ],
+ [ "\t", qr/(default.*verbose.*postgres=\# )?/s ],
+ [ "d\t\n", qr/default.*postgres=\# /s ],
+ # misc
+ [ "\\cd \t", qr/(.*postgres=\# )?\\cd /s ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # object type completion,
+ # strange: the list is not seen from the test driver
+ # but the coverage works as expected.
+ #[ "CREATE \t", qr/i(MATERIALIZED VIEW.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "CREATE \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "DROP \t", qr/(UNLOGGED.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "DROP \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "ALTER \t", qr/(TABLESPACE.*postgres=\# )/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ALTER \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ANALYZE (\t", qr/(SKIP_LOCKED.*postgres=\# )?ANALYZE \(/s ],
+ [ "V\tOF\t", qr/VERBOSE OFF/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # SET
+ [ "SET \t", qr/(zero_damaged_pages.*postgres=\# )?SET /s ],
+ [ "da\t", qr/datestyle / ],
+ [ "\t", qr/TO / ],
+ #[ "\t", qr/(YMD.*postgres=\# SET datestyle TO )?/s ],
+ [ "\tDE\t", qr/DEFAULT/ ],
+ [ ";\n", qr/SET.*postgres=\# /s ],
+ # SET completion on a GUC variable,
+ #[ "SET default_with\t", qr/default_with_oids / ],
+ #[ "TO \t", qr/(YES.*postgres=\# SET default_with_oids )?TO /s ],
+ #[ "D\t", qr/DEFAULT/ ],
+ #[ ";\n", qr/SET.*postgres=\# / ],
+ # psql variables
+ [ "\\ec\t", qr/\\echo / ],
+ [ ":q\t", qr/QUIET/ ],
+ [ ":'PO\t", qr/:'PORT'/ ],
+ [ ":\"HISTC\t", qr/:"HISTCONTROL"/ ],
+ [ "\n", qr/(on|off).*'\d+'.*"[a-z]+".*postgres=\# /s ],
+ # file
+ [ "COPY regress_psql_tap_1_t1 FROM \t", qr/(postgres=\# COPY .* FROM )?/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ ],
+ 'ipsql tab 1');
+
+
+# final cleanup
+psql('', 0, '-- cleanup objects
+DROP VIEW regress_psql_tap_1_v1;
+DROP FUNCTION regress_psql_tap_1_f1;
+DROP TABLE regress_psql_tap_1_t1;
+DROP USER regress_psql_tap_1;
+', [ qr{DROP VIEW}, qr{DROP FUNCTION}, qr{DROP TABLE}, qr{DROP ROLE} ], [ qr{^$} ], 'psql -- drop stuff');
+
+$node->stop();
+done_testing();
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index b685b35282..2692c99e17 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -92,6 +92,7 @@ $node->issues_sql_like(
$node->command_checks_all(
[ 'vacuumdb', '--analyze', '--table', 'vacview', 'postgres' ],
0,
+ '',
[qr/^.*vacuuming database "postgres"/],
[qr/^WARNING.*cannot vacuum non-tables or special system tables/s],
'vacuumdb with view');
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 270bd6c856..5faafce12e 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1676,6 +1676,25 @@ sub command_checks_all
=pod
+=item $node->icommand_checks(cmd, ...)
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local $ENV{PGHOST} = $self->host;
+ local $ENV{PGPORT} = $self->port;
+
+ TestLib::icommand_checks(@_);
+ return;
+}
+
+=pod
+
=item $node->issues_sql_like(cmd, expected_sql, test_name)
Run a command on the node, then verify that $expected_sql appears in the
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 6195c21c59..848597695c 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -21,6 +21,7 @@ use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
+use Expect;
# specify a recent enough version of Test::More to support the done_testing() function
use Test::More 0.87;
@@ -48,6 +49,7 @@ our @EXPORT = qw(
command_like_safe
command_fails_like
command_checks_all
+ icommand_checks
$windows_os
);
@@ -528,9 +530,10 @@ sub command_fails_like
}
# Run a command and check its status and outputs.
-# The 5 arguments are:
+# The 6 arguments are:
# - cmd: ref to list for command, options and arguments to run
# - ret: expected exit status
+# - in: standard input
# - out: ref to list of re to be checked against stdout (all must match)
# - err: ref to list of re to be checked against stderr (all must match)
# - test_name: name of test
@@ -538,12 +541,13 @@ sub command_checks_all
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($cmd, $expected_ret, $out, $err, $test_name) = @_;
+ my ($cmd, $expected_ret, $in, $out, $err, $test_name) = @_;
+ $in = '' if not defined $in;
# run command
my ($stdout, $stderr);
print("# Running: " . join(" ", @{$cmd}) . "\n");
- IPC::Run::run($cmd, '>', \$stdout, '2>', \$stderr);
+ IPC::Run::run($cmd, '<', \$in, '>', \$stdout, '2>', \$stderr);
# See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
my $ret = $?;
@@ -570,4 +574,63 @@ sub command_checks_all
return;
}
+# Run a command and check its status and outputs.
+# The 5 arguments are:
+# - cmd: ref to list for command, options and arguments to run
+# - ret: expected exit status
+# - timeout: for interactions
+# - init: initial output before interacting
+# - inout: [ [ input, list of output checks ], ... ]
+# - name: of the test
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($cmd, $expected_ret, $timeout, $init, $inout, $name) = @_;
+
+
+ my $ps = Expect->spawn(@$cmd);
+ ok(defined $ps, "$name command spawn: @$cmd");
+ return unless defined $ps;
+
+ #$ps->slave->stty(qw(raw -echo));
+ $ps->slave->stty(qw(raw));
+ for my $test (@$inout)
+ {
+ #warn "test: @$test";
+ my ($in, @out) = @$test;
+ #warn "in: $in";
+ #warn "out: @out";
+ $ps->send($in);
+ for my $o (@out)
+ {
+ my $ok = $ps->expect($timeout, '-re', $o);
+ # check header on first output
+ if (defined $init)
+ {
+ # beware of race conditions...
+ ok($ps->before =~ $init,
+ "$name header matches /$init/");
+ undef $init;
+ }
+ ok($ok, "$name output matches /$o/");
+ if (not $ok)
+ {
+ print STDERR "before: ###" . $ps->before . "###\n" if $ps->before;
+ print STDERR "match: ###" . $ps->match . "###\n" if $ps->match;
+ print STDERR "after: ###" . $ps->after . "###\n" if $ps->after;
+ print STDERR "error: ###" . $ps->error . "###\n" if $ps->error;
+ }
+ }
+ }
+ $ps->soft_close();
+
+ # check status
+ my $ret = $ps->exitstatus;
+ ok($ret == $expected_ret,
+ "$name status (got $ret vs expected $expected_ret)");
+
+ return;
+}
+
1;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index ef534a36a0..bbe4a69370 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4348,22 +4348,60 @@ bar 'bar' "bar"
:try_to_quit
\echo `nosuchcommand` :foo :'foo' :"foo"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
- \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+ \a
+ \C arg1
+ \c arg1 arg2 arg3 arg4
+ \cd arg1
+ \conninfo
\copy arg1 arg2 arg3 arg4 arg5 arg6
- \copyright \dt arg1 \e arg1 arg2
+ \copyright
+ SELECT 1 as one, 2, 3 \crosstabview
+ \dt arg1
+ \e arg1 arg2
\ef whole_line
\ev whole_line
- \echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
- \g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
- \o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
- \reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+ \echo arg1 arg2 arg3 arg4 arg5
+ \echo arg1
+ \encoding arg1
+ \errverbose
+ \f arg1
+ \g arg1
+ \gx arg1
+ \gexec
+ SELECT 1 AS one \gset
+ \h
+ \?
+ \html
+ \i arg1
+ \ir arg1
+ \l arg1
+ \lo arg1 arg2
+invalid command \lo
+ \lo_list
+ \o arg1
+ \p
+ \password arg1
+ \prompt arg1 arg2
+ \pset arg1 arg2
+ \q
+ \reset
+ \s arg1
+ \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
+ \setenv arg1 arg2
\sf whole_line
\sv whole_line
- \t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+ \t arg1
+ \T arg1
+ \timing arg1
+ \unset arg1
+ \w arg1
+ \watch arg1
+ \x arg1
-- \else here is eaten as part of OT_FILEPIPE argument
\w |/no/such/file \else
-- \endif here is eaten as part of whole-line argument
\! whole_line \endif
+ \z
\else
\echo 'should print #8-1'
should print #8-1
@@ -4382,6 +4420,17 @@ should print #8-1
\echo '#10-2 ok, variable no_such_variable is not defined'
#10-2 ok, variable no_such_variable is not defined
\endif
+-- elif after true branch
+\if true
+ \echo 'should print #11-1'
+should print #11-1
+\elif true
+ \echo 'should not print #11-2'
+\elif false
+ \echo 'should not print #11-3'
+\else
+ \echo 'should not print #11-4'
+\endif
SELECT :{?i} AS i_is_defined;
i_is_defined
--------------
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 2e37984962..1160855e01 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -911,22 +911,59 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
:try_to_quit
\echo `nosuchcommand` :foo :'foo' :"foo"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
- \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+ \a
+ \C arg1
+ \c arg1 arg2 arg3 arg4
+ \cd arg1
+ \conninfo
\copy arg1 arg2 arg3 arg4 arg5 arg6
- \copyright \dt arg1 \e arg1 arg2
+ \copyright
+ SELECT 1 as one, 2, 3 \crosstabview
+ \dt arg1
+ \e arg1 arg2
\ef whole_line
\ev whole_line
- \echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
- \g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
- \o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
- \reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+ \echo arg1 arg2 arg3 arg4 arg5
+ \echo arg1
+ \encoding arg1
+ \errverbose
+ \f arg1
+ \g arg1
+ \gx arg1
+ \gexec
+ SELECT 1 AS one \gset
+ \h
+ \?
+ \html
+ \i arg1
+ \ir arg1
+ \l arg1
+ \lo arg1 arg2
+ \lo_list
+ \o arg1
+ \p
+ \password arg1
+ \prompt arg1 arg2
+ \pset arg1 arg2
+ \q
+ \reset
+ \s arg1
+ \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
+ \setenv arg1 arg2
\sf whole_line
\sv whole_line
- \t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+ \t arg1
+ \T arg1
+ \timing arg1
+ \unset arg1
+ \w arg1
+ \watch arg1
+ \x arg1
-- \else here is eaten as part of OT_FILEPIPE argument
\w |/no/such/file \else
-- \endif here is eaten as part of whole-line argument
\! whole_line \endif
+ \z
\else
\echo 'should print #8-1'
\endif
@@ -945,6 +982,17 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
\echo '#10-2 ok, variable no_such_variable is not defined'
\endif
+-- elif after true branch
+\if true
+ \echo 'should print #11-1'
+\elif true
+ \echo 'should not print #11-2'
+\elif false
+ \echo 'should not print #11-3'
+\else
+ \echo 'should not print #11-4'
+\endif
+
SELECT :{?i} AS i_is_defined;
SELECT NOT :{?no_such_var} AS no_such_var_is_not_defined;
Attached is a rebase after TestLib.pm got a documentation in 6fcc40b1.
The attached patch improves psql code coverage by adding a specific TAP test.
The 1709 tests take 4 seconds CPU (6.3 elapsed time) on my laptop.The infrastructure is updated to require perl module "Expect", allowing to
test interactive features such as tab completion and prompt changes.Coverage in "src/bin/psql" jumps from 40.0% of lines and 41.9% of functions
to 78.4% of lines and 98.1% of functions with "check-world".
--
Fabien.
Attachments:
psql-tap-2.patchtext/x-diff; name=psql-tap-2.patchDownload
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index b7d36b65dd..aabc27ab6f 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -506,6 +506,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*checksum verification failed/s],
'pg_basebackup reports checksum mismatch');
@@ -526,6 +527,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt2" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*further.*failures.*will.not.be.reported/s],
'pg_basebackup does not report more than 5 checksum mismatches');
@@ -542,6 +544,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt3" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*7 total checksum verification failures/s],
'pg_basebackup correctly report the total number of checksum mismatches');
diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl
index 59228b916c..cf4811d382 100644
--- a/src/bin/pg_checksums/t/002_actions.pl
+++ b/src/bin/pg_checksums/t/002_actions.pl
@@ -62,6 +62,7 @@ sub check_relation_corruption
'--filenode', $relfilenode_corrupted
],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data for single relfilenode on tablespace $tablespace"
@@ -71,6 +72,7 @@ sub check_relation_corruption
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data on tablespace $tablespace");
@@ -203,6 +205,7 @@ sub fail_corrupt
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/^$/],
[qr/could not read block 0 in file.*$file\":/],
"fails for corrupted data in $file");
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
index 3b63ad230f..ebe9b80a52 100644
--- a/src/bin/pg_controldata/t/001_pg_controldata.pl
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -33,6 +33,7 @@ close $fh;
command_checks_all(
[ 'pg_controldata', $node->data_dir ],
0,
+ '',
[
qr/WARNING: Calculated CRC checksum does not match value stored in file/,
qr/WARNING: invalid WAL segment size/
diff --git a/src/bin/pg_resetwal/t/002_corrupted.pl b/src/bin/pg_resetwal/t/002_corrupted.pl
index f9940d7fc5..1990669d26 100644
--- a/src/bin/pg_resetwal/t/002_corrupted.pl
+++ b/src/bin/pg_resetwal/t/002_corrupted.pl
@@ -30,6 +30,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/
@@ -46,6 +47,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index b82d3f65c4..01010914fe 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -50,7 +50,7 @@ sub pgbench
push @cmd, @args;
- $node->command_checks_all(\@cmd, $stat, $out, $err, $name);
+ $node->command_checks_all(\@cmd, $stat, '', $out, $err, $name);
# cleanup?
#unlink @filenames or die "cannot unlink files (@filenames): $!";
diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl
index f7fa18418b..b58f3548c3 100644
--- a/src/bin/pgbench/t/002_pgbench_no_server.pl
+++ b/src/bin/pgbench/t/002_pgbench_no_server.pl
@@ -25,7 +25,7 @@ sub pgbench
my ($opts, $stat, $out, $err, $name) = @_;
print STDERR "opts=$opts, stat=$stat, out=$out, err=$err, name=$name";
command_checks_all([ 'pgbench', split(/\s+/, $opts) ],
- $stat, $out, $err, $name);
+ $stat, '', $out, $err, $name);
return;
}
@@ -52,7 +52,7 @@ sub pgbench_scripts
push @cmd, '-f', $filename;
}
}
- command_checks_all(\@cmd, $stat, $out, $err, $name);
+ command_checks_all(\@cmd, $stat, '', $out, $err, $name);
return;
}
diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore
index c2862b12d6..d324c1c1fa 100644
--- a/src/bin/psql/.gitignore
+++ b/src/bin/psql/.gitignore
@@ -3,3 +3,4 @@
/sql_help.c
/psql
+/tmp_check
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 69bb297fe7..9473ab01cb 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -60,8 +60,15 @@ uninstall:
clean distclean:
rm -f psql$(X) $(OBJS) lex.backup
+ rm -rf tmp_check
# files removed here are supposed to be in the distribution tarball,
# so do not clean them in the clean/distclean rules
maintainer-clean: distclean
rm -f sql_help.h sql_help.c psqlscanslash.c
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 774cc764ff..d7c0fc0c1e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -307,6 +307,7 @@ describeTablespaces(const char *pattern, bool verbose)
*
* a for aggregates
* n for normal
+ * p for procedure
* t for trigger
* w for window
*
diff --git a/src/bin/psql/t/001_psql.pl b/src/bin/psql/t/001_psql.pl
new file mode 100644
index 0000000000..dafa68e63e
--- /dev/null
+++ b/src/bin/psql/t/001_psql.pl
@@ -0,0 +1,869 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+my $node = get_new_node('main');
+$node->init();
+$node->start();
+
+# create a file under the test directory
+# return its full path
+sub create_test_file
+{
+ my ($fname, $contents) = @_;
+ my $fn = $node->basedir . '/' . $fname;
+ #ok(not -e $fn, "$fn must not already exists");
+ append_to_file($fn, $contents);
+ return $fn;
+}
+
+# invoke psql
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected exit status
+# - in: input stream
+# - out: list of re to check on stdout
+# - err: list of re to check on stderr
+# - name: of the test
+# - more: more raw arguments
+sub psql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $in, $out, $err, $name, @more) = @_;
+ $opts =~ s/^!// or push @more, '-X';
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+ $node->command_checks_all(\@cmd, $stat, $in, $out, $err, $name);
+ return;
+}
+
+# invoke psql interactively, making it believe it is connected to a tty
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected final exit status
+# - timeout: for interactions, in second
+# - inout: list of input expected re
+# - name: of the test
+# - remove: tell to remove init/start/end checks
+sub ipsql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $timeout, $inout, $name, $remove) = @_;
+
+ # build command to run
+ my @more = ();
+ @more = ('-X') unless $opts =~ /^!/;
+ $opts =~ s/^!//;
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+
+ # check it is running at start and end, and exit cleanly
+ unless ($remove)
+ {
+ unshift @$inout, [ "\\echo STARTING :VERSION_NUM\n", qr/STARTING \d+.*postgres=\# /s ];
+ push @$inout, [ "\\echo ENDING :VERSION_NUM\\q\n", qr/ENDING \d+$/s ];
+ }
+
+ # call the stuff
+ $node->icommand_checks(
+ \@cmd, $stat, $timeout,
+ $remove ? undef : qr/psql .*Type "help" for help..*postgres=# /s,
+ $inout, $name);
+ return;
+}
+
+my $EMPTY = [ qr/^$/ ];
+
+#
+# check various options
+#
+# this cannot be tested from within a SQL script
+psql('!--help', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql --help');
+psql('!-?', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql -?');
+psql('!--version', 0, '',
+ [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql --version');
+psql('--help=variables', 0, '',
+ [ qr{VERSION_NAME}, qr{unicode_border_linestyle}, qr{PSQL_EDITOR} ],
+ $EMPTY, 'psql --help=variables');
+# look for plenty of backslash-commands
+psql('--help=commands', 0, '',
+ [ qr{\\copyright}, qr{\\pset}, qr{(?!VERSION_NAME)} ],
+ $EMPTY, 'psql --help=commands');
+
+# tested elsewhere: -a -A -d -e -h -L
+psql('-b', 0, "SELECT 1/0;\n",
+ $EMPTY, [ qr{STATEMENT: SELECT 1/0}, qr{ERROR: division by zero} ], 'psql -b');
+psql('-c \\q', 0, '', $EMPTY, $EMPTY, 'psql -c');
+psql('-c \\l -c \\l', 0, '', [ qr{template0.*template0}s ], $EMPTY, 'psql -c -c');
+psql('', 0, '', [ qr{(?!1234)}, qr{5678}s ], $EMPTY, 'psql -c ;;', ('-c', 'SELECT 1234; SELECT 5678;'));
+psql('-E', 0, "\\l\n", [ qr{\* QUERY \*}, qr{template0} ], $EMPTY, 'psql -E');
+psql('-A -F _', 0, "SELECT 54, 32;\n", [ qr{54_32} ], $EMPTY, 'psql -F _');
+psql('-H', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -H');
+psql('-l', 0, '', [ qr{template0}, qr{template1} ], $EMPTY, 'psql -l');
+psql('-n', 0, "\\q\n", $EMPTY, $EMPTY, 'psql -n');
+psql('-P format=html', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -P format=html');
+psql('-P tuples_only', 0, "\\pset\n", [ qr{tuples_only.*on} ], $EMPTY, 'psql -P tuples_only');
+psql('-A -R_', 0, "VALUES (1), (2);\n", [ qr{_1_2_} ], $EMPTY, 'psql -R _');
+psql('-s', 0, "SELECT 1;\n", [ qr{verify command}, qr{SELECT 1}, qr{press return to proceed} ], $EMPTY, 'psql -s');
+psql('-S', 0, "SELECT 1 AS one\nSELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], $EMPTY, 'psql -S');
+psql('-H -T width=100', 0, "SELECT 1 AS one;\n", [ qr{table.*width=100} ], $EMPTY, 'psql -T ...');
+psql('-X -V', 0, '', [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql -X -V');
+psql('', 0, "\\echo :VERSION_NUM\n", [ qr{\d{6}} ], $EMPTY, 'psql -- show VERSION_NUM');
+psql('-v VERSION_NUM', 0, "\\echo :VERSION_NUM\n", [ qr{:VERSION_NUM} ], $EMPTY, 'psql -v ...');
+psql('-v VERSION_NUM=bla', 0, "\\echo :VERSION_NUM\n", [ qr{bla} ], $EMPTY, 'psql -v ...=.');
+psql('-w', 0, '', $EMPTY, $EMPTY, 'psql -w');
+# nope, interacts on tty
+#psql('-W', 0, "foo\n", [ qr{^$} ], [ qr{^$} ], 'psql -W');
+psql('-x', 0, "SELECT 1 AS one, 2 AS two;\n", [ qr{one \| 1.*two \| 2}s ], $EMPTY, 'psql -x');
+# some issue, \0 is not welcome somewhere
+#psql('-A -z', "SELECT 1 AS one, 2 AS two;\n", [ qr{one.two}s, qr{1.2}s ], [ qr{^$} ], 'psql -z');
+#psql('-A -0', "SELECT 1 AS one, 2 AS two;\n", [ qr{two.1}s ], [ qr{^$} ], 'psql -0');
+psql('-1', 0, "SELECT 54;\nSELECT 32;\n", [ qr{54}, qr{32} ], $EMPTY, 'psql -1');
+psql('-X -?', 0, '',
+ [ qr{interactive terminal}, qr{Usage:}, qr{Connection options:} ], $EMPTY, 'psql -X -?');
+psql('--csv', 0, "SELECT 1 AS one, 2 AS two\n", [ qr{one,two}, qr{1,2} ], $EMPTY, 'psql --csv');
+
+#
+# create some objects to help \d tests later
+#
+psql('', 0, '-- create some objects
+CREATE USER regress_psql_tap_1;
+ALTER ROLE regress_psql_tap_1 SET enable_indexscan TO off;
+CREATE TABLE regress_psql_tap_1_t1(data TEXT NOT NULL);
+CREATE FUNCTION regress_psql_tap_1_f1() RETURNS INTEGER AS $$ SELECT 54321 $$ LANGUAGE SQL;
+CREATE VIEW regress_psql_tap_1_v1 AS SELECT 54321 AS one;
+', [ qr{CREATE ROLE}, qr{ALTER ROLE} ], [ qr{^$} ], 'psql -- create stuff');
+
+#
+# check interactive help
+#
+# this is mostly for coverage, including occasional negatives.
+# we certainly do not want to have an output file with the whole doc.
+my @backslash_help = (
+ # \h command, list of stdout matches
+ [ 'no-help', [ qr{No help available for} ] ],
+ [ '' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ [ ' ' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ # lists
+ [ 'ALTER', [ qr{AGGREGATE}, qr{ROLE}, qr{VIEW}, qr{(?!ACCESS METHOD)}, qr{(?!TRANSFORM)} ] ],
+ [ 'ALTER TEXT SEARCH', [ qr{CONFIGURATION}, qr{DICTIONARY}, qr{PARSER}, qr{TEMPLATE} ] ],
+ [ 'ALTER FOREIGN', [ qr{DATA WRAPPER}, qr{TABLE} ] ],
+ [ 'CREATE', [ qr{ACCESS METHOD}, qr{TRANSFORM} ] ],
+ [ 'DROP', [ qr{ACCESS METHOD}, qr{VIEW} ] ],
+ # details
+ [ 'ABORT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'ALTER AGGREGATE', [ qr{RENAME TO}, qr{(?!CONVERSION)} ] ],
+ [ 'ALTER CONVERSION', [ qr{SET SCHEMA}, qr{(?!AGGREGATE)} ] ],
+ [ 'ALTER COLLATION', [ qr{REFRESH VERSION} ] ],
+ [ 'ALTER DATABASE', [ qr{CONNECTION LIMIT} ] ],
+ [ 'ALTER DEFAULT PRIVILEGES', [ qr{GRANT} ] ],
+ [ 'ALTER DOMAIN', [ qr{VALIDATE CONSTRAINT} ] ],
+ [ 'ALTER EVENT TRIGGER', [ qr{DISABLE} ] ],
+ [ 'ALTER EXTENSION', [ qr{OPERATOR FAMILY} ] ],
+ [ 'ALTER FOREIGN DATA WRAPPER', [ qr{VALIDATOR} ] ],
+ [ 'ALTER FOREIGN TABLE', [ qr{SET STORAGE} ] ],
+ [ 'ALTER FUNCTION', [ qr{PARALLEL} ] ],
+ [ 'ALTER GROUP', [ qr{DROP USER} ] ],
+ [ 'ALTER INDEX', [ qr{ALL IN TABLESPACE} ] ],
+ [ 'ALTER LANGUAGE', [ qr{PROCEDURAL}, qr{(?!INDEX)} ] ],
+ [ 'ALTER LARGE OBJECT', [ qr{OWNER TO} ] ],
+ [ 'ALTER MATERIALIZED VIEW', [ qr{SET TABLESPACE} ] ],
+ [ 'ALTER OPERATOR', [ qr{JOIN =} ] ],
+ [ 'ALTER OPERATOR CLASS', [ qr{SET SCHEMA}, qr{(?!LANGUAGE)} ] ],
+ [ 'ALTER OPERATOR FAMILY', [ qr{FUNCTION} ] ],
+ [ 'ALTER POLICY', [ qr{WITH CHECK} ] ],
+ [ 'ALTER PROCEDURE', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER PUBLICATION', [ qr{DROP TABLE} ] ],
+ [ 'ALTER ROLE', [ qr{BYPASSRLS} ] ],
+ [ 'ALTER ROUTINE', [ qr{LEAKPROOF} ] ],
+ [ 'ALTER RULE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER SCHEMA', [ qr{OWNER TO} ] ],
+ [ 'ALTER SEQUENCE', [ qr{NO MAXVALUE} ] ],
+ [ 'ALTER SERVER', [ qr{VERSION} ] ],
+ [ 'ALTER STATISTICS', [ qr{SET SCHEMA} ] ],
+ [ 'ALTER SUBSCRIPTION', [ qr{REFRESH PUBLICATION} ] ],
+ [ 'ALTER SYSTEM', [ qr{RESET ALL} ] ],
+ [ 'ALTER TABLE', [ qr{ALL IN TABLESPACE}, qr{DISABLE ROW LEVEL SECURITY} ] ],
+ [ 'ALTER TABLESPACE', [ qr{RESET} ] ],
+ [ 'ALTER TEXT SEARCH CONFIGURATION', [ qr{ALTER MAPPING FOR} ] ],
+ [ 'ALTER TEXT SEARCH DICTIONARY', [ qr{OWNER TO} ] ],
+ [ 'ALTER TEXT SEARCH PARSER', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TEXT SEARCH TEMPLATE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TRIGGER', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER TYPE', [ qr{RENAME ATTRIBUTE} ] ],
+ [ 'ALTER USER', [ qr{NOREPLICATION}, qr{(?!MAPPING)} ] ],
+ [ 'ALTER USER MAPPING', [ qr{SERVER} ] ],
+ [ 'ALTER VIEW', [ qr{DROP DEFAULT} ] ],
+ [ 'ANALYZE', [ qr{VERBOSE}, qr{SKIP_LOCKED} ] ],
+ [ 'BEGIN', [ qr{ISOLATION LEVEL} ] ],
+ [ 'CALL', [] ],
+ [ 'CHECKPOINT', [] ],
+ [ 'CLOSE', [ qr{ALL} ] ],
+ [ 'CLUSTER', [ qr{VERBOSE} ] ],
+ [ 'COMMENT', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'COMMIT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'COMMIT PREPARED', [] ],
+ [ 'COPY', [ qr{FORMAT}, qr{FORCE_NOT_NULL} ] ],
+ [ 'CREATE ACCESS METHOD', [ qr{HANDLER} ] ],
+ [ 'CREATE AGGREGATE', [ qr{SSPACE}, qr{SORTOP}, qr{FINALFUNC} ] ],
+ [ 'CREATE CAST', [ qr{AS IMPLICIT} ] ],
+ [ 'CREATE COLLATION', [ qr{LC_CTYPE}, qr{DETERMINISTIC} ] ],
+ [ 'CREATE CONVERSION', [ qr{FOR} ] ],
+ [ 'CREATE DATABASE', [ qr{ALLOW_CONNECTIONS} ] ],
+ [ 'CREATE DOMAIN', [ qr{COLLATE} ] ],
+ [ 'CREATE EVENT TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE EXTENSION', [ qr{FROM} ] ],
+ [ 'CREATE FOREIGN DATA WRAPPER', [ qr{NO VALIDATOR} ] ],
+ [ 'CREATE FOREIGN TABLE', [ qr{SERVER} ] ],
+ [ 'CREATE FUNCTION', [ qr{CALLED ON NULL INPUT}, qr{(?!PROCEDURE)} ] ],
+ [ 'CREATE GROUP', [ qr{ADMIN} ] ],
+ [ 'CREATE INDEX', [ qr{CONCURRENTLY}, qr{WHERE} ] ],
+ [ 'CREATE LANGUAGE', [ qr{TRUSTED} ] ],
+ [ 'CREATE MATERIALIZED VIEW', [ qr{DATA} ] ],
+ [ 'CREATE OPERATOR', [ qr{LEFTARG} ] ],
+ [ 'CREATE OPERATOR CLASS', [ qr{FOR ORDER BY} ] ],
+ [ 'CREATE OPERATOR FAMILY', [ qr{USING} ] ],
+ [ 'CREATE POLICY', [ qr{PERMISSIVE} ] ],
+ [ 'CREATE PROCEDURE', [ qr{SECURITY INVOKER}, qr{(?!FUNCTION)} ] ],
+ [ 'CREATE PUBLICATION', [ qr{FOR ALL TABLES} ] ],
+ [ 'CREATE ROLE', [ qr{SYSID} ] ],
+ [ 'CREATE RULE', [ qr{ALSO} ] ],
+ [ 'CREATE SCHEMA', [ qr{AUTHORIZATION} ] ],
+ [ 'CREATE SEQUENCE', [ qr{NO MINVALUE} ] ],
+ [ 'CREATE SERVER', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'CREATE STATISTICS', [ qr{FROM} ] ],
+ [ 'CREATE SUBSCRIPTION', [ qr{CONNECTION} ] ],
+ [ 'CREATE TABLE', [ qr{PARTITION BY} ] ],
+ [ 'CREATE TABLE AS', [ qr{ON COMMIT} ] ],
+ [ 'CREATE TABLESPACE', [ qr{LOCATION} ] ],
+ [ 'CREATE TEXT SEARCH CONFIGURATION', [ qr{PARSER} ] ],
+ [ 'CREATE TEXT SEARCH DICTIONARY', [ qr{TEMPLATE} ] ],
+ [ 'CREATE TEXT SEARCH PARSER', [ qr{GETTOKEN} ] ],
+ [ 'CREATE TEXT SEARCH TEMPLATE', [ qr{LEXIZE} ] ],
+ [ 'CREATE TRANSFORM', [ qr{FROM SQL WITH FUNCTION} ] ],
+ [ 'CREATE TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE TYPE', [ qr{SUBTYPE} ] ],
+ [ 'CREATE USER', [ qr{PASSWORD NULL} ] ],
+ [ 'CREATE USER MAPPING', [ qr{SERVER} ] ],
+ [ 'CREATE VIEW', [ qr{CASCADED} ] ],
+ [ 'DEALLOCATE', [ qr{PREPARE} ] ],
+ [ 'DECLARE', [ qr{INSENSITIVE} ] ],
+ [ 'DELETE', [ qr{WHERE CURRENT OF} ] ],
+ [ 'DISCARD', [ qr{PLANS} ] ],
+ [ 'DO', [ qr{LANGUAGE} ] ],
+ [ 'DROP ACCESS METHOD', [ qr{CASCADE} ] ],
+ [ 'DROP AGGREGATE', [ qr{ORDER BY} ] ],
+ [ 'DROP CAST', [ qr{RESTRICT} ] ],
+ [ 'DROP COLLATION', [ qr{RESTRICT} ] ],
+ [ 'DROP CONVERSION', [ qr{RESTRICT} ] ],
+ [ 'DROP DATABASE', [ qr{IF EXISTS} ] ],
+ [ 'DROP DOMAIN', [ qr{RESTRICT} ] ],
+ [ 'DROP EVENT TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP EXTENSION', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN DATA WRAPPER', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP FUNCTION', [ qr{RESTRICT} ] ],
+ [ 'DROP GROUP', [ qr{IF EXISTS} ] ],
+ [ 'DROP INDEX', [ qr{RESTRICT} ] ],
+ [ 'DROP LANGUAGE', [ qr{RESTRICT} ] ],
+ [ 'DROP MATERIALIZED VIEW', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR CLASS', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR FAMILY', [ qr{RESTRICT} ] ],
+ [ 'DROP OWNED', [ qr{CURRENT_USER} ] ],
+ [ 'DROP POLICY', [ qr{RESTRICT} ] ],
+ [ 'DROP PROCEDURE', [ qr{RESTRICT} ] ],
+ [ 'DROP PUBLICATION', [ qr{RESTRICT} ] ],
+ [ 'DROP ROLE', [ qr{IF EXISTS} ] ],
+ [ 'DROP ROUTINE', [ qr{RESTRICT} ] ],
+ [ 'DROP RULE', [ qr{RESTRICT} ] ],
+ [ 'DROP SCHEMA', [ qr{RESTRICT} ] ],
+ [ 'DROP SEQUENCE', [ qr{RESTRICT} ] ],
+ [ 'DROP SERVER', [ qr{RESTRICT} ] ],
+ [ 'DROP STATISTICS', [ qr{IF EXISTS} ] ],
+ [ 'DROP SUBSCRIPTION', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLESPACE', [ qr{IF EXISTS} ] ],
+ [ 'DROP TEXT SEARCH CONFIGURATION', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH DICTIONARY', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH PARSER', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH TEMPLATE', [ qr{RESTRICT} ] ],
+ [ 'DROP TRANSFORM', [ qr{LANGUAGE} ] ],
+ [ 'DROP TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP TYPE', [ qr{RESTRICT} ] ],
+ [ 'DROP USER', [ qr{IF EXISTS} ] ],
+ [ 'DROP USER MAPPING', [ qr{SERVER} ] ],
+ [ 'DROP VIEW', [ qr{RESTRICT} ] ],
+ [ 'END', [ qr{TRANSACTION}, qr{CHAIN}, qr{(?!COMMIT)} ] ],
+ [ 'EXECUTE', [ ] ],
+ [ 'EXPLAIN', [ qr{VERBOSE} ] ],
+ [ 'FETCH', [ qr{RELATIVE} ] ],
+ [ 'GRANT', [ qr{ALL TABLES} ] ],
+ [ 'IMPORT FOREIGN SCHEMA', [ qr{FROM SERVER} ] ],
+ [ 'INSERT', [ qr{ON CONFLICT} ] ],
+ [ 'LISTEN', [ ] ],
+ [ 'LOAD', [ ] ],
+ [ 'LOCK', [ qr{NOWAIT} ] ],
+ [ 'MOVE', [ qr{FORWARD} ] ],
+ [ 'NOTIFY', [ ] ],
+ [ 'PREPARE', [ qr{AS} ] ],
+ [ 'PREPARE TRANSACTION', [ ] ],
+ [ 'REASSIGN OWNED', [ qr{SESSION_USER} ] ],
+ [ 'REFRESH MATERIALIZED VIEW', [ qr{CONCURRENTLY} ] ],
+ [ 'REINDEX', [ qr{DATABASE} ] ],
+ [ 'RELEASE', [ qr{SAVEPOINT} ] ],
+ [ 'RESET', [ qr{RESET ALL} ] ],
+ [ 'REVOKE', [ qr{PRIVILEGES} ] ],
+ [ 'ROLLBACK', [ qr{CHAIN} ] ],
+ [ 'ROLLBACK PREPARED', [ ]],
+ [ 'SAVEPOINT', [ ] ],
+ [ 'SECURITY LABEL', [ qr{SUBSCRIPTION} ] ],
+ [ 'SELECT', [ qr{RECURSIVE} ] ],
+ [ 'SET', [ qr{SESSION} ] ],
+ [ 'SET CONSTRAINTS', [ qr{IMMEDIATE} ] ],
+ [ 'SET ROLE', [ qr{ROLE NONE} ] ],
+ [ 'SET SESSION AUTHORIZATION', [ qr{DEFAULT} ] ],
+ [ 'SET TRANSACTION', [ qr{SNAPSHOT} ] ],
+ [ 'SHOW', [ qr{SHOW ALL} ] ],
+ [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ],
+ [ 'TABLE', [ qr{ONLY} ] ], # hmmm...
+ [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ],
+ [ 'UNLISTEN', [ ] ],
+ [ 'UPDATE', [ qr{RETURNING} ] ],
+ [ 'VACUUM', [ qr{FREEZE} ] ],
+ [ 'VALUES', [ qr{ORDER BY} ] ],
+ [ 'WITH', [ qr{RECURSIVE} ] ], # SELECT duplicate?
+);
+
+for my $h (@backslash_help)
+{
+ my ($cmd, $out) = @$h;
+ push @$out, qr{$cmd};
+ psql('', 0, "\\h $cmd\n", $out, [ qr{^$} ], "psql -- \\h $cmd");
+}
+
+# special cases
+psql('', 0, "\\h ROLLBACK TO SAVEPOINT\n", [ qr{ROLLBACK}, qr{SAVEPOINT} ],
+ [ qr{^$} ], "psql -- \\h ROLLBACK TO SAVEPOINT");
+psql('', 0, "\\h SELECT INTO\n", [ qr{SELECT}, qr{INTO} ],
+ [ qr{^$} ], "psql -- \\h SELECT INTO");
+
+#
+# check describe and other backslash commands
+#
+# the output can vary significantly, especially with +
+my @backslash_out = (
+ # empty
+ [ "\\d\n", [ qr{List of relations}, qr{regress_psql_tap_1_t1}, qr{(?!pg_locks)} ] ],
+ [ "\\dS\n", [ qr{List of relations}, qr{pg_locks} ] ],
+ # aggregates
+ [ "\\da\n", [ qr{List of aggregate functions}, qr{(?!Description)} ] ],
+ [ "\\da+\n", [ qr{List of aggregate functions}, qr{(Description)} ] ],
+ [ "\\daS\n", [ qr{array_agg}, qr{avg}, qr{bit_and}, qr{bit_or}, qr{count}, qr{max} ] ],
+ # access methods
+ [ "\\dA\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{(?!Description)} ] ],
+ [ "\\dA+\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{Description} ] ],
+ # tablespaces
+ [ "\\db\n", [ qr{List of tablespaces}, qr{pg_default}, qr{pg_global}, qr{(?!Size)} ] ],
+ [ "\\db+ pg_def*\n", [ qr{List of tablespaces}, qr{pg_default}, qr{(?!pg_global)}, qr{Size} ] ],
+ # functions
+ [ "\\df\n", [ qr{List of functions} ] ],
+ [ "\\df+\n", [ qr{List of functions} ] ],
+ [ "\\dftS\n", [ qr{RI_}, qr{Type}, qr{(?!Volatility)}, qr{(?!Parallel)} ] ],
+ [ "\\dftS+\n", [ qr{Volatility}, qr{Parallel} ] ],
+ [ "\\dfwS\n", [ qr{lag}, qr{dense_rank}, qr{(?!Owner)}, qr{(?!Language)} ] ],
+ [ "\\dfwS+\n", [ qr{cume_dist}, qr{Owner}, qr{Language} ] ],
+ [ "\\dfnS\n", [ qr{abbrev}, qr{(?!bit_or)} ] ],
+ [ "\\dfaS\n", [ qr{bit_and}, qr{(?!abs)} ] ],
+ [ "\\dfpS\n", [ qr{List of functions} ] ],
+ [ "\\dfwtS\n", [ qr{RI_FKey_}, qr{last_value}, qr{nth_value}, qr{(?!bit_and)} ] ],
+ # type
+ [ "\\dTS charac*\n", [ qr{List of data types}, qr{character varying}, qr{(?!double)}, qr{(?!Size)} ] ],
+ [ "\\dTS+\n", [ qr{aclitem}, qr{Size} ] ],
+ # operator
+ [ "\\doS\n", [ qr{List of operators}, qr{!~\*} ] ],
+ [ "\\doS+\n", [ qr{<->}, qr{Result type}] ],
+ # databases
+ [ "\\l\n", [ qr{List of databases}, qr{template0}, qr{template1}, qr{(?!Size)} ] ],
+ [ "\\l a*\n", [ qr{(?!template[01])} ] ],
+ [ "\\l+\n", [ qr{Size}, qr{Encoding} ] ],
+ [ "\\list\n", [ qr{List of databases} ] ],
+ # permissions: well tested
+ # default acls (empty)
+ [ "\\ddp\n", [ qr{Default access privileges} ] ],
+ # descriptions (empty)
+ [ "\\dd\n", [ qr{Object descriptions} ] ],
+ # tables: well tested
+ # roles/users/groups
+ [ "\\du\n", [ qr{Superuser}, qr{regress_psql_tap_1}, qr{List of roles}, qr{(?!Description)} ] ],
+ [ "\\du+\n", [ qr{Superuser}, qr{List of roles}, qr{Description} ] ],
+ [ "\\dg\n", [ qr{Superuser}, qr{List of roles} ] ],
+ # role settings
+ [ "\\drds\n", [ qr{List of settings} ] ],
+ [ "\\drds regress_* *\n", [ qr{List of settings} ] ],
+ # index/...
+ [ "\\diS\n", [ qr{List of relations}, qr{pg_am_oid_index} ] ],
+ [ "\\diS+\n", [ qr{Table} ] ],
+ # partition tables: well tested
+ # large objects
+ [ "\\dl", [ qr{Large objects} ] ],
+ # languages
+ [ "\\dL\n", [ qr{List of languages}, qr{plpgsql}, qr{(?!Trusted)} ] ],
+ [ "\\dL+\n", [ qr{plpgsql}, qr{Trusted} ] ],
+ # domains
+ [ "\\dD\n", [ qr{List of domains}, qr{(?!Access privileges)} ] ],
+ [ "\\dD+\n", [ qr{List of domains}, qr{Access privileges} ] ],
+ # conversions
+ [ "\\dcS\n", [ qr{List of conversions}, qr{UTF8}, qr{(?!Description)} ] ],
+ [ "\\dcS+\n", [ qr{List of conversions}, qr{LATIN1}, qr{Description} ] ],
+ # event triggers (empty)
+ [ "\\dy\n", [ qr{List of event triggers}, qr{(?!Description)} ] ],
+ [ "\\dy+\n", [ qr{List of event triggers}, qr{Description} ] ],
+ # casts
+ [ "\\dC\n", [ qr{List of casts}, qr{timestamp without time zone}, qr{(?!Description)} ] ],
+ [ "\\dC+\n", [ qr{Function}, qr{Implicit}, qr{Description} ] ],
+ # collations
+ [ "\\dOS\n", [ qr{List of collations}, qr{Deterministic}, qr{(?!Description)} ] ],
+ [ "\\dOS+\n", [ qr{POSIX}, qr{Description} ] ],
+ # schemas
+ [ "\\dn\n", [ qr{List of schemas}, qr{Owner}, qr{(?!Description)} ] ],
+ [ "\\dn+\n", [ qr{Access privileges}, qr{Description} ] ],
+ # text search misc.
+ [ "\\dFp\n", [ qr{List of text search parsers}, qr{default} ] ],
+ [ "\\dFp+\n", [ qr{hword}, qr{numword} ] ],
+ [ "\\dFd\n", [ qr{List of text search dictionaries}, qr{simple} ] ],
+ [ "\\dFd+\n", [ qr{Init options}, qr{Description} ] ],
+ [ "\\dFt\n", [ qr{List of text search templates}, qr{simple} ] ],
+ [ "\\dFt+\n", [ qr{Lexize}, qr{Description} ] ],
+ [ "\\dF\n", [ qr{List of text search configurations}, qr{simple} ] ],
+ [ "\\dF+\n", [ qr{email}, qr{url_path} ] ],
+ # extensions
+ [ "\\dx\n", [ qr{List of installed extensions}, qr{Version}, qr{plpgsql} ] ],
+ [ "\\dx+\n", [ qr{Objects in extension}, qr{plpgsql_call_handler} ] ],
+ # other backslash commands
+ [ "\\f\n", [ qr{Field separator is} ] ],
+ [ "\\H\\H\n", [ qr{Output format is html.* is aligned}s ] ],
+ [ "\\\?\n", [ qr{General}, qr{Help}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\C foobar\n", [ qr{Title is "foobar"} ] ],
+ [ "\\cd /\n\\cd\n", [ qr{^$} ] ],
+ [ "\\conninfo\n", [ qr{You are connected to database} ] ],
+ [ "\\copy (SELECT 5432 UNION SELECT 2345 ORDER BY 1) TO STDOUT\n", [ qr/\b2345\b.*\b5432\b/s ] ],
+ [ "\\copyright\n", [ qr/The Regents of the University of California/ ] ],
+ [ "\\encoding\n", [ qr/./ ] ],
+ [ "\\encoding :ENCODING\n", $EMPTY ],
+ [ "\\errverbose\n", [ qr{There is no previous error} ] ],
+ [ "\\lo_list\n", [ qr{Large objects} ] ],
+ [ "\\if true\\q\\endif\n", $EMPTY ],
+ # ???
+ #[ "SELECT md5('hello world');\n\\s\n", [ qr{5eb63bbbe0}, qr{SELECT md5} ] ],
+ [ "\\set\n", [ qr{ENCODING = }, qr{VERSION_NUM = } ] ],
+ [ "\\set COMP_KEYWORD_CASE preserve-lower\n\\set COMP_KEYWORD_CASE lower\n" .
+ "\\set COMP_KEYWORD_CASE upper\n\\echo :COMP_KEYWORD_CASE", [ qr/upper/ ] ],
+ [ "\\set HISTCONTROL ignorespace\n\\set HISTCONTROL ignoredups\n" .
+ "\\set HISTCONTROL ignoreboth\n\\echo :HISTCONTROL", [ qr/ignoreboth/ ] ],
+ [ "\\set ECHO_HIDDEN on\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/template0/ ] ],
+ [ "\\set ECHO_HIDDEN noexec\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/(?!template0)/ ] ],
+ [ "\\set LAST_ERROR_MESSAGE foo bla\n\\echo :LAST_ERROR_MESSAGE\n", [ qr{foobla} ] ],
+ [ "\\set AUTOCOMMIT off\n\\set ON_ERROR_ROLLBACK on\nSELECT 5432 AS pg;\nSELECT 2345 AS pg;\n",
+ [ qr{5432}, qr{2345} ] ],
+ [ "\\set ON_ERROR_ROLLBACK interactive\n\\echo :ON_ERROR_ROLLBACK\n", [ qr{interactive} ] ],
+ [ "\\set VERBOSITY verbose\n\\echo :VERBOSITY\n", [ qr{verbose} ] ],
+ [ "\\set foo bla\n\\echo :foo\\unset foo\\echo :foo\n", [ qr{^bla.*:foo}s ] ],
+ [ "\\set foo bla\n\\echo :'foo' :\"foo\"\n", [ qr{'bla'}, qr{"bla"} ] ],
+ [ "\\setenv FOO bla\n\\setenv FOO\n", [ qr{^$} ] ],
+ [ "\\timing\nSELECT 1;\n\\timing\n", [ qr{Timing is on.*Timing is off}s, qr(Time: \d+\.\d{3} ms) ] ],
+ [ "\\timing ON\n\\timing OFF\n", [ qr{Timing is on.*Timing is off}s ] ],
+ [ "\\T foo\n", [ qr{Table attributes are "foo"} ] ],
+ [ "\\T\n", [ qr{Table attributes unset} ] ],
+ # help variants
+ [ "\\?\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? commands\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? bad\n", [ qr{General} ] ],
+ [ "\\? variables\n",
+ [ qr{psql variables:}, qr{Display settings:}, qr{Environment variables} ] ],
+ [ "\\? options\n",
+ [ qr{interactive terminal}, qr{Report bugs} ] ],
+ # pset format
+ [ "\\pset format aligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is aligned}, qr{ 1 \| 2} ] ],
+ [ "\\pset format asciidoc\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is asciidoc}, qr{\|1 \|2} ] ],
+ [ "\\pset format csv\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is csv}, qr{1,2} ] ],
+ [ "\\pset format html\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is html}, qr{<td align="right">1</td>} ] ],
+ [ "\\pset format latex\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex}, qr{1 \& 2} ] ],
+ [ "\\pset format latex-longtable\n\\pset format\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex-longtable}, qr{\\raggedright\{1\}} ] ],
+ [ "\\pset format troff-ms\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is troff-ms}, qr{1\t2} ] ],
+ [ "\\pset format unaligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is unaligned}, qr{1\|2} ] ],
+ [ "\\pset format wrapped\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is wrapped}, qr{ 1 \| 2} ] ],
+ [ "\\pset format\n", [ qr{Output format is aligned} ] ],
+ # pset linestyle
+ [ "\\pset linestyle ascii\n", [ qr{Line style is ascii} ] ],
+ [ "\\pset linestyle old-ascii\n", [ qr{Line style is old-ascii} ] ],
+ [ "\\pset linestyle unicode\n", [ qr{Line style is unicode} ] ],
+ # pset unicode_*_linestyle
+ [ "\\pset unicode_header_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_header_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_border_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_border_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_column_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_column_linestyle double\n", [ qr{"double"} ] ],
+);
+
+for my $h (@backslash_out)
+{
+ my ($in, $out) = @$h;
+ psql('', 0, $in, $out, [ qr{^$} ], "psql -- $in");
+}
+
+# test some errors
+my @backslash_err = (
+ [ "\\unknown stuff\n", [ qr/invalid command / ] ],
+ [ "\\cd /no/such/dir\n", [ qr/could not change directory to/ ] ],
+ [ "\\copy\n", [ qr/arguments required/ ] ],
+ [ "\\copy bad stuff on the line\n", [ qr/parse error at \"/ ] ],
+ [ "\\copy binary (\n", [ qr/parse error at end of line/ ] ],
+ [ "\\copy public.regress_psql_tap_1_t1(data) to '/no/such/file'", [ qr{/no/such/file} ] ],
+ [ "\\encoding no-such-encoding\n", [ qr/invalid encoding name/ ] ],
+ [ "BAD;\n\\errverbose\n", [ qr/syntax error at or near "BAD"/, qr/\b42601\b/ ] ],
+ [ "\\dfX\n", [ qr/invalid command/ ] ],
+ [ "\\dPX\n", [ qr/invalid command/ ] ],
+ [ "\\drX\n", [ qr/invalid command/ ] ],
+ [ "\\dRX\n", [ qr/invalid command/ ] ],
+ [ "\\dFX\n", [ qr/invalid command/ ] ],
+ [ "\\deX\n", [ qr/invalid command/ ] ],
+ [ "\\dX\n", [ qr/invalid command/ ] ],
+ [ "\\i\n", [ qr/missing required argument/ ] ],
+ [ "\\ir\n", [ qr/missing required argument/] ],
+ [ "\\lo_bad\n", [ qr/invalid command/ ] ],
+ [ "\\lo_export\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import /no/such/file\n", [ qr{no/such/file} ] ],
+ [ "\\lo_unlink\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_unlink -1\n", [ qr/large object 4294967295 does not exist/ ] ],
+ [ "\\setenv\n", [ qr/missing required argument/ ] ],
+ [ "\\setenv PG_REGRESS_TEST=1\n", [ qr/environment variable name must not contain "="/ ] ],
+ [ "\\unset\n", [ qr/missing required argument/ ] ],
+ [ "\\set foo \`no-such-command\`\n", [ qr/no-such-command/ ] ],
+ [ "\\set === xxx\n", [ qr/invalid variable name/ ] ],
+ [ "\\w\n", [ qr/missing required argument/ ] ],
+ [ "\\pset format bad\n", [ qr/allowed formats are aligned, asciidoc/ ] ],
+ [ "\\pset linestyle bad\n", [ qr/allowed line styles are ascii, old-ascii, unicode/ ] ],
+ [ "\\pset unicode_border_linestyle bad\n", [ qr/allowed Unicode border line styles are single, double/ ] ],
+ [ "\\pset unicode_column_linestyle bad\n", [ qr/allowed Unicode column line styles are single, double/ ] ],
+ [ "\\pset unicode_header_linestyle bad\n", [ qr/allowed Unicode header line styles are single, double/ ] ],
+ [ "COPY regress_psql_tap_1_t1 FROM STDIN \\watch 0.01\n", [ qr/watch cannot be used with COPY/ ] ],
+ [ "\\watch 0.01\n", [ qr/watch cannot be used with an empty query/ ] ],
+ # check variable constraints values
+ [ "\\set ECHO bad\n", [ qr{none, errors, queries, all} ] ],
+ [ "\\set ECHO_HIDDEN bad\n", [ qr{on, off, noexec} ] ],
+ [ "\\set ON_ERROR_ROLLBACK bad\n", [ qr{on, off, interactive} ] ],
+ [ "\\set COMP_KEYWORD_CASE bad\n", [ qr{lower, upper, preserve-lower, preserve-upper} ] ],
+ [ "\\set HISTCONTROL bad\n", [ qr{none, ignorespace, ignoredups, ignoreboth} ] ],
+ [ "\\set VERBOSITY bad\n", [ qr{default, verbose, terse, sqlstate} ] ],
+ [ "\\set SHOW_CONTEXT bad\n", [ qr{never, errors, always} ] ],
+);
+
+for my $h (@backslash_err)
+{
+ my ($in, $err) = @$h;
+ psql('-L /dev/null', 0, $in, [ qr{^$} ], $err, "psql -- $in");
+}
+
+# other errors
+
+# \timing
+psql('', 0, "\\timing bad\n",
+ [ qr{Timing is off} ], [ qr{unrecognized value} ], 'psql \timing error');
+
+# check stdout vs stderr output
+psql('', 0, "\\echo hello\n\\warn world\n\\q\n",
+ [ qr{^hello$} ], [ qr{^world$} ], 'psql in/out/err');
+
+# \watch test
+psql('', 0,
+ "CREATE TEMP SEQUENCE tmp_seq MAXVALUE 3 NO CYCLE;\n" .
+ "\\t on\n" .
+ "\\timing\n" .
+ "SELECT 'x=' || NEXTVAL('tmp_seq') \\watch 0.01\n",
+ [ qr/x=1\b.*x=2\b.*x=3\b/s, qr/(?!x=[04-9])/ ],
+ [ qr/nextval: reached maximum value of sequence/ ],
+ 'psql watch');
+
+# skip un*x-specific zone if it does not look like un*x
+goto END_UNIX_ZONE
+ unless -e "/dev/null" and -d "/tmp" and -x "/bin/cat" and -x "/bin/echo";
+
+# this probably only works under UN*X-like systems
+psql('', 0, "\\setenv PSQL_EDITOR echo\nSELECT 1 AS one;\n\\e\n\\p\n",
+ [ qr/one.*one/s, qr/SELECT 1/ ], $EMPTY, 'psql edit');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ef regress_psql_tap_1_f1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ef');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ev regress_psql_tap_1_v1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ev');
+psql('', 0, "\\setenv FOO bla\\set foo \`echo \$FOO\`\n\\echo :foo\n",
+ [ qr/bla/ ], $EMPTY, 'psql backtick');
+psql('', 0, "\\setenv FOO blabla\n\\! echo \$FOO\n",
+ [ qr/blabla/ ], $EMPTY, 'psql !');
+
+psql('-o /dev/null', 0, "SELECT 5432 AS pg\n", $EMPTY, $EMPTY, 'psql -o null');
+psql('', 0, "\\o /dev/null\nSELECT 1;\n", $EMPTY, $EMPTY, 'psql \o null');
+psql('', 0, "\\o | cat\nSELECT 5432 AS pg;\n\\o\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql \o cat');
+
+psql('', 0, "\\s /dev/null\n", $EMPTY, $EMPTY, 'psql \s null');
+
+psql('', 0, "SELECT 5432 AS pg;\n\\w /dev/null\n",
+ [ qr/pg.*\b5432\b/s ], $EMPTY, 'psql \w null');
+
+psql('', 0, "SELECT 1\\g /dev/null\n",
+ $EMPTY, $EMPTY, 'psql \g null');
+psql('', 0, "SELECT 5432\\g | cat\n",
+ [ qr/\b5432\n/ ], $EMPTY, 'psql \g cat');
+
+psql('--log-file=/dev/null', 0, "SELECT 5432 AS pg\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql -L null');
+
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) FROM PROGRAM 'echo moe'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy echo');
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) TO PROGRAM 'cat'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy cat'); # :-)
+
+END_UNIX_ZONE:
+
+psql('', 0, "\\i /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \i error');
+psql('', 0, "\\i ~/there/is/no/such/file\n",
+ [ qr{^$} ], [ qr{there/is/no/such/file} ], 'psql \i tilde');
+
+# no such file
+psql('', 0, "\\o /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \o error');
+
+psql('', 0, "\\w /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \w error');
+
+psql('-L /no/such/file', 1, '',
+ [ qr{^$} ], [ qr{could not open log file} ], 'psql -L bad');
+
+# \copy errors
+psql('', 0, "\\copy regress_psql_tap_1_t1 to program '/no/such/prgm'",
+ [ qr/COPY 1/ ], [ qr{/no/such/prgm} ], 'psql copy to bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from program '/no/such/prgm'",
+ [ qr/COPY 0/ ], [ qr{/no/such/prgm} ], 'psql copy from bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 to '/tmp'",
+ $EMPTY, [ qr{directory} ], 'psql copy to tmp');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from '/tmp'",
+ $EMPTY, [ qr/directory/ ], 'psql copy to tmp');
+
+# misc errors
+psql('-P format=bad', 1, '', $EMPTY,
+ [ qr/allowed formats are aligned/, qr/could not set printing parameter/ ], 'psql -P bad');
+psql('-Q', 1, '', $EMPTY, [ qr/Try ".* --help" for more information/ ], 'psql -BAD');
+
+#
+# .psqlrc
+#
+# note: reading the system wide psqlrc cannot be avoided
+#
+#import env;
+my $psqlrc = create_test_file('regress_psql_tap_1_psqlrc', "SELECT 1 AS one\n");
+$ENV{PSQLRC} = $psqlrc;
+psql('!', 0, "SELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], [ qr{^$} ], 'psql .psqlrc');
+delete $ENV{PSQLRC};
+ok(unlink $psqlrc, "unlink $psqlrc");
+
+#
+# large objects
+#
+my $lofile = create_test_file('regress_psql_tap_1_lo', '0123456789ABCDEF' x 1024);
+my $esc_lofile = $lofile;
+$esc_lofile =~ s/'/''/g;
+psql('-H', 0,
+ "\\lo_import '$esc_lofile' 'regress psql TAP lo'\n\\lo_list\n",
+ [ qr{lo_import \d+}, qr{Large objects}, qr{regress psql TAP lo} ], [ qr{^$} ], 'psql lo 1');
+ok(unlink $lofile, "unlink $lofile (1)");
+psql('', 0,
+ "SELECT oid AS loboid\n" .
+ "FROM pg_catalog.pg_largeobject_metadata\n" .
+ "WHERE pg_catalog.obj_description(oid, 'pg_largeobject') = 'regress psql TAP lo'\\gset\n" .
+ "\\echo loboid = :loboid\n" .
+ "\\pset tuples_only on\n" .
+ "SELECT 'lo extract: ' || lo_get(:loboid, 826, 3)::TEXT AS extract;\n" .
+ "\\lo_export :loboid '/no/such/dir/stuff'\n" . # this one fails
+ "\\lo_export :loboid '$esc_lofile'\n" .
+ "\\lo_unlink :loboid\n",
+ [ qr{loboid = \d+}, qr{lo extract: \\x414243} ],
+ [ qr{could not open file "/no/such/dir/stuff"} ],
+ 'psql lo 2');
+ok(-e $lofile, "re-created $lofile");
+ok(unlink $lofile, "unlink $lofile (2)");
+# TODO
+#ok(not -e $lofile, "file $lofile is removed");
+
+#
+# INTERACTIVE STUFF
+#
+# pretty slow... espacially the tab-completion thing
+#
+# disable pager to work around pagination interactions
+
+# basic test
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ "\\echo hello world\n", qr/hello world.*postgres=\# /s ],
+ [ "help\n", qr/You are using.*\\copyright.*quit.*postgres=\# /s ],
+ [ "SELECT\nhelp\n\\r\n", qr/help or press control-[CD].*postgres=\# /s ],
+ [ "SELECT\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT (\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT \$\$\nquit\n\$\$\\r\n", qr/Use control-[CD] to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT '\nquit'\\r\n", qr/quit'.*postgres=\# /s ],
+ [ "SELECT '\n\\q\n", qr/Use control-[CD] to quit.*postgres'\# /s ],
+ [ "'\\r\n", qr/buffer reset.*postgres=\# /s ],
+ [ "SELECT 1\n\\p\n\\r\n", qr/SELECT 1.*SELECT 1.*buffer reset.*postgres=\#/s ],
+ #[ "\\prompt tmp_var\nfoobla\n\\echo hello :tmp_var\n\\unset foo\n\\echo world :tmp_var\n",
+ # qr/hello foobla.*world :tmp_var.*postgres=\# /s ],
+ [ "\\password\n", qr/Enter new password: / ],
+ [ "foo\n", qr/Enter it again: / ],
+ [ "bla\n", qr/Passwords didn't match.*postgres=\# /s ],
+ [ "exit\n" ],
+ ],
+ 'ipsql basic', 1);
+
+# signals
+#TODO: reliably check for un*x vs windows
+my $stop = -e '/dev/null' ? "\004" : "\003";
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ $stop, qr/\\q/ ],
+ ],
+ 'ipsql signals 1', 1);
+
+# prompt
+ipsql('-P pager', 0, 5,
+ [ # default prompts
+ [ "SELECT 1 +\n", qr/postgres-\# / ],
+ [ "(2 * \n", qr/postgres\(\# / ],
+ [ "3) + '\n", qr/postgres\'\# / ],
+ [ "4' + \$\$\n", qr/postgres\$\# /],
+ [ "5\$\$ AS \"\n", qr/postgres\"\# / ],
+ [ "seize\" /*\n", qr/postgres\*\# / ],
+ [ " ignored */;\n", qr/\bseize\b.*\b16\b.*postgres=\# /s ],
+ [ "\\if false\n", qr/postgres\@\# / ],
+ [ "\\echo IGNORED\n", qr/command ignored.*if block.*postgres\@\# /s ],
+ # BUG: prompt with - instead of @
+ [ "SELECT 1;\n", qr/query ignored.*if block.*postgres.\# /s ],
+ [ "\\endif\n", qr/postgres=\# / ],
+ [ "\\set SINGLELINE ON\n", qr/postgres\^\# / ],
+ [ "\\set SINGLELINE OFF\n", qr/postgres=\# / ],
+ [ "COPY regress_psql_tap_1_t1(data) FROM STDIN;\n", qr/>> / ],
+ [ "hello\nworld\n\\.\n", qr/COPY 2\b.*postgres=\# /s ],
+ # set new prompts (not tested: %`)
+ [ "\\set PROMPT1 '%%%[%]%?%M%m%>%n%/%~%#%p%R%x%l%:QUIET:%040'\n", qr/%.*\d+(on|off) /s ],
+ [ "\\set PROMPT1 '1> '\n", qr/\b1> / ],
+ [ "\\set PROMPT2 '2> '\n", qr/\b1> / ],
+ [ "\\set PROMPT3 '3> '\n", qr/\b1> / ],
+ [ "COPY regress_psql_tap_1_t1(data)\n", qr/\b2> / ],
+ [ "FROM STDIN;\n", qr/\b3> / ],
+ [ "calvin\nhobbes\nsusie\nmoe\n\\.\n", qr/COPY 4\b.*\b1> /s ],
+ # reset prompts
+ [ "\\set PROMPT1 '%/%R%# '\n", qr/PROMPT1.*postgres=# /s ],
+ [ "\\set PROMPT2 '%/%R%# '\n", qr/PROMPT2.*postgres=# /s ],
+ [ "\\set PROMPT3 '>> '\n", qr/PROMPT3.*postgres=# /s ],
+ ],
+ 'ipsql prompt 1');
+
+# tab-complation
+ipsql('-P pager', 0, 5,
+ [ # commands
+ [ "SEL\t", qr/SELECT / ],
+ [ "* FROM pg_catalog.pg_ca\t", qr/pg_catalog\.pg_cast / ],
+ [ "LIMIT 17;\n", qr/\b17 rows.*postgres=\# /s ],
+ # backslash commands
+ [ "\\d\t", qr/(\\da.*\\dy.*postgres=# )?\\d/s ],
+ [ "y\n", qr/List of event triggers.*postgres=\# /s ],
+ # pset
+ [ "\\pset \t", qr/(unicode_column_linestyle.*postgres=\# )?\\pset/s ],
+ [ "tu\t", qr/tuples_only / ],
+ [ "off\n", qr/postgres=\# / ],
+ [ "\\pset unicode_b\t", qr/unicode_border_linestyle / ],
+ [ "\t", qr/(double.*single.*postgres=\# )?/s ],
+ [ "double\n", qr/Unicode border line style is "double".*postgres=\# /s ],
+ # set
+ [ "\\set VERB\t", qr/VERBOSITY / ],
+ [ "\t", qr/(default.*verbose.*postgres=\# )?/s ],
+ [ "d\t\n", qr/default.*postgres=\# /s ],
+ # misc
+ [ "\\cd \t", qr/(.*postgres=\# )?\\cd /s ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # object type completion,
+ # strange: the list is not seen from the test driver
+ # but the coverage works as expected.
+ #[ "CREATE \t", qr/i(MATERIALIZED VIEW.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "CREATE \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "DROP \t", qr/(UNLOGGED.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "DROP \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "ALTER \t", qr/(TABLESPACE.*postgres=\# )/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ALTER \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ANALYZE (\t", qr/(SKIP_LOCKED.*postgres=\# )?ANALYZE \(/s ],
+ [ "V\tOF\t", qr/VERBOSE OFF/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # SET
+ [ "SET \t", qr/(zero_damaged_pages.*postgres=\# )?SET /s ],
+ [ "da\t", qr/datestyle / ],
+ [ "\t", qr/TO / ],
+ #[ "\t", qr/(YMD.*postgres=\# SET datestyle TO )?/s ],
+ [ "\tDE\t", qr/DEFAULT/ ],
+ [ ";\n", qr/SET.*postgres=\# /s ],
+ # SET completion on a GUC variable,
+ #[ "SET default_with\t", qr/default_with_oids / ],
+ #[ "TO \t", qr/(YES.*postgres=\# SET default_with_oids )?TO /s ],
+ #[ "D\t", qr/DEFAULT/ ],
+ #[ ";\n", qr/SET.*postgres=\# / ],
+ # psql variables
+ [ "\\ec\t", qr/\\echo / ],
+ [ ":q\t", qr/QUIET/ ],
+ [ ":'PO\t", qr/:'PORT'/ ],
+ [ ":\"HISTC\t", qr/:"HISTCONTROL"/ ],
+ [ "\n", qr/(on|off).*'\d+'.*"[a-z]+".*postgres=\# /s ],
+ # file
+ [ "COPY regress_psql_tap_1_t1 FROM \t", qr/(postgres=\# COPY .* FROM )?/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ ],
+ 'ipsql tab 1');
+
+
+# final cleanup
+psql('', 0, '-- cleanup objects
+DROP VIEW regress_psql_tap_1_v1;
+DROP FUNCTION regress_psql_tap_1_f1;
+DROP TABLE regress_psql_tap_1_t1;
+DROP USER regress_psql_tap_1;
+', [ qr{DROP VIEW}, qr{DROP FUNCTION}, qr{DROP TABLE}, qr{DROP ROLE} ], [ qr{^$} ], 'psql -- drop stuff');
+
+$node->stop();
+done_testing();
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index b685b35282..2692c99e17 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -92,6 +92,7 @@ $node->issues_sql_like(
$node->command_checks_all(
[ 'vacuumdb', '--analyze', '--table', 'vacview', 'postgres' ],
0,
+ '',
[qr/^.*vacuuming database "postgres"/],
[qr/^WARNING.*cannot vacuum non-tables or special system tables/s],
'vacuumdb with view');
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 270bd6c856..5faafce12e 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1676,6 +1676,25 @@ sub command_checks_all
=pod
+=item $node->icommand_checks(cmd, ...)
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local $ENV{PGHOST} = $self->host;
+ local $ENV{PGPORT} = $self->port;
+
+ TestLib::icommand_checks(@_);
+ return;
+}
+
+=pod
+
=item $node->issues_sql_like(cmd, expected_sql, test_name)
Run a command on the node, then verify that $expected_sql appears in the
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 92199792eb..fa9dabbe7b 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -53,6 +53,7 @@ use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
+use Expect;
# specify a recent enough version of Test::More to support the
# done_testing() function
@@ -81,6 +82,7 @@ our @EXPORT = qw(
command_like_safe
command_fails_like
command_checks_all
+ icommand_checks
$windows_os
);
@@ -792,6 +794,8 @@ Arguments:
=item C<ret>: Expected exit code
+=item C<in>: Stdin for command
+
=item C<out>: Expected stdout from command
=item C<err>: Expected stderr from command
@@ -806,12 +810,13 @@ sub command_checks_all
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($cmd, $expected_ret, $out, $err, $test_name) = @_;
+ my ($cmd, $expected_ret, $in, $out, $err, $test_name) = @_;
+ $in = '' if not defined $in;
# run command
my ($stdout, $stderr);
print("# Running: " . join(" ", @{$cmd}) . "\n");
- IPC::Run::run($cmd, '>', \$stdout, '2>', \$stderr);
+ IPC::Run::run($cmd, '<', \$in, '>', \$stdout, '2>', \$stderr);
# See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
my $ret = $?;
@@ -840,6 +845,82 @@ sub command_checks_all
=pod
+=item icommand_checks(cmd, ret, timeout, init, inout, name)
+
+Run a command and check its status and outputs.
+Arguments:
+
+=over
+
+=item C<cmd> ref to list for command, options and arguments to run
+
+=item C<ret> expected exit status
+
+=item C<timeout> for interactions
+
+=item C<init> initial output before interacting
+
+=item C<inout> [ [ input, list of output checks ], ... ]
+
+=item C<name> of the test
+
+=back
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($cmd, $expected_ret, $timeout, $init, $inout, $name) = @_;
+
+
+ my $ps = Expect->spawn(@$cmd);
+ ok(defined $ps, "$name command spawn: @$cmd");
+ return unless defined $ps;
+
+ #$ps->slave->stty(qw(raw -echo));
+ $ps->slave->stty(qw(raw));
+ for my $test (@$inout)
+ {
+ #warn "test: @$test";
+ my ($in, @out) = @$test;
+ #warn "in: $in";
+ #warn "out: @out";
+ $ps->send($in);
+ for my $o (@out)
+ {
+ my $ok = $ps->expect($timeout, '-re', $o);
+ # check header on first output
+ if (defined $init)
+ {
+ # beware of race conditions...
+ ok($ps->before =~ $init,
+ "$name header matches /$init/");
+ undef $init;
+ }
+ ok($ok, "$name output matches /$o/");
+ if (not $ok)
+ {
+ print STDERR "before: ###" . $ps->before . "###\n" if $ps->before;
+ print STDERR "match: ###" . $ps->match . "###\n" if $ps->match;
+ print STDERR "after: ###" . $ps->after . "###\n" if $ps->after;
+ print STDERR "error: ###" . $ps->error . "###\n" if $ps->error;
+ }
+ }
+ }
+ $ps->soft_close();
+
+ # check status
+ my $ret = $ps->exitstatus;
+ ok($ret == $expected_ret,
+ "$name status (got $ret vs expected $expected_ret)");
+
+ return;
+}
+
+=pod
+
=back
=cut
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index ef534a36a0..bbe4a69370 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4348,22 +4348,60 @@ bar 'bar' "bar"
:try_to_quit
\echo `nosuchcommand` :foo :'foo' :"foo"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
- \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+ \a
+ \C arg1
+ \c arg1 arg2 arg3 arg4
+ \cd arg1
+ \conninfo
\copy arg1 arg2 arg3 arg4 arg5 arg6
- \copyright \dt arg1 \e arg1 arg2
+ \copyright
+ SELECT 1 as one, 2, 3 \crosstabview
+ \dt arg1
+ \e arg1 arg2
\ef whole_line
\ev whole_line
- \echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
- \g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
- \o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
- \reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+ \echo arg1 arg2 arg3 arg4 arg5
+ \echo arg1
+ \encoding arg1
+ \errverbose
+ \f arg1
+ \g arg1
+ \gx arg1
+ \gexec
+ SELECT 1 AS one \gset
+ \h
+ \?
+ \html
+ \i arg1
+ \ir arg1
+ \l arg1
+ \lo arg1 arg2
+invalid command \lo
+ \lo_list
+ \o arg1
+ \p
+ \password arg1
+ \prompt arg1 arg2
+ \pset arg1 arg2
+ \q
+ \reset
+ \s arg1
+ \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
+ \setenv arg1 arg2
\sf whole_line
\sv whole_line
- \t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+ \t arg1
+ \T arg1
+ \timing arg1
+ \unset arg1
+ \w arg1
+ \watch arg1
+ \x arg1
-- \else here is eaten as part of OT_FILEPIPE argument
\w |/no/such/file \else
-- \endif here is eaten as part of whole-line argument
\! whole_line \endif
+ \z
\else
\echo 'should print #8-1'
should print #8-1
@@ -4382,6 +4420,17 @@ should print #8-1
\echo '#10-2 ok, variable no_such_variable is not defined'
#10-2 ok, variable no_such_variable is not defined
\endif
+-- elif after true branch
+\if true
+ \echo 'should print #11-1'
+should print #11-1
+\elif true
+ \echo 'should not print #11-2'
+\elif false
+ \echo 'should not print #11-3'
+\else
+ \echo 'should not print #11-4'
+\endif
SELECT :{?i} AS i_is_defined;
i_is_defined
--------------
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 2e37984962..1160855e01 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -911,22 +911,59 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
:try_to_quit
\echo `nosuchcommand` :foo :'foo' :"foo"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
- \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+ \a
+ \C arg1
+ \c arg1 arg2 arg3 arg4
+ \cd arg1
+ \conninfo
\copy arg1 arg2 arg3 arg4 arg5 arg6
- \copyright \dt arg1 \e arg1 arg2
+ \copyright
+ SELECT 1 as one, 2, 3 \crosstabview
+ \dt arg1
+ \e arg1 arg2
\ef whole_line
\ev whole_line
- \echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
- \g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
- \o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
- \reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+ \echo arg1 arg2 arg3 arg4 arg5
+ \echo arg1
+ \encoding arg1
+ \errverbose
+ \f arg1
+ \g arg1
+ \gx arg1
+ \gexec
+ SELECT 1 AS one \gset
+ \h
+ \?
+ \html
+ \i arg1
+ \ir arg1
+ \l arg1
+ \lo arg1 arg2
+ \lo_list
+ \o arg1
+ \p
+ \password arg1
+ \prompt arg1 arg2
+ \pset arg1 arg2
+ \q
+ \reset
+ \s arg1
+ \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
+ \setenv arg1 arg2
\sf whole_line
\sv whole_line
- \t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+ \t arg1
+ \T arg1
+ \timing arg1
+ \unset arg1
+ \w arg1
+ \watch arg1
+ \x arg1
-- \else here is eaten as part of OT_FILEPIPE argument
\w |/no/such/file \else
-- \endif here is eaten as part of whole-line argument
\! whole_line \endif
+ \z
\else
\echo 'should print #8-1'
\endif
@@ -945,6 +982,17 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
\echo '#10-2 ok, variable no_such_variable is not defined'
\endif
+-- elif after true branch
+\if true
+ \echo 'should print #11-1'
+\elif true
+ \echo 'should not print #11-2'
+\elif false
+ \echo 'should not print #11-3'
+\else
+ \echo 'should not print #11-4'
+\endif
+
SELECT :{?i} AS i_is_defined;
SELECT NOT :{?no_such_var} AS no_such_var_is_not_defined;
On Tue, Sep 03, 2019 at 08:06:43AM +0200, Fabien COELHO wrote:
Attached is a rebase after TestLib.pm got a documentation in
6fcc40b1.
I am not completely sure what to think about this patch, but here are
some high-level comments.
+=item $node->icommand_checks(cmd, ...)
+
+=cut
+
+sub icommand_checks
Surely this can have a better description, like say
PostgresNode::command_checks_all.
Is Expect compatible down to perl 5.8.0 which is the minimum required
for the TAP tests (see src/test/perl/README)?
There are cases where we don't support tab completion, aka no
USE_READLINE. So tests would need to be skipped.
- \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+ \a
+ \C arg1
Why are you changing that? Your patch does not touch the logic of
psql. Could it make sense as a separate patch to shape better the
tests?
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -307,6 +307,7 @@ describeTablespaces(const char *pattern, bool
verbose)
* a for aggregates
* n for normal
+ * p for procedure
This is a separate issue, fixed.
--
Michael
Bonjour Michaᅵl,
+=item $node->icommand_checks(cmd, ...) + +=cut + +sub icommand_checksSurely this can have a better description, like say
PostgresNode::command_checks_all.
Ok.
Is Expect compatible down to perl 5.8.0 which is the minimum required
for the TAP tests (see src/test/perl/README)?
I think so. It looks like this has existed for a very long time (22
years?), but I cannot test it simply against a perl 5.8.
There are cases where we don't support tab completion, aka no
USE_READLINE. So tests would need to be skipped.
Good catch. I added a skip if it detects that history/readline is
disabled.
- \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo + \a + \C arg1 Why are you changing that?
AFAICR this is because the coverage was not the same:-) Some backslash
commands just skip silently to the end of the line, so that intermediate
\commands on the same line are not recognized/processed the same, so I
moved everything on one line to avoid this.
Your patch does not touch the logic of psql. Could it make sense as a
separate patch to shape better the tests?
Nope, this is not just reshaping, it is really about improving coverage.
--- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -307,6 +307,7 @@ describeTablespaces(const char *pattern, bool verbose) * a for aggregates * n for normal + * p for procedure This is a separate issue, fixed.
Ok.
Attached v3 which fixes the outlined issues.
--
Fabien.
Attachments:
psql-tap-3.patchtext/x-diff; name=psql-tap-3.patchDownload
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index b7d36b65dd..aabc27ab6f 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -506,6 +506,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*checksum verification failed/s],
'pg_basebackup reports checksum mismatch');
@@ -526,6 +527,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt2" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*further.*failures.*will.not.be.reported/s],
'pg_basebackup does not report more than 5 checksum mismatches');
@@ -542,6 +544,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt3" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*7 total checksum verification failures/s],
'pg_basebackup correctly report the total number of checksum mismatches');
diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl
index 59228b916c..cf4811d382 100644
--- a/src/bin/pg_checksums/t/002_actions.pl
+++ b/src/bin/pg_checksums/t/002_actions.pl
@@ -62,6 +62,7 @@ sub check_relation_corruption
'--filenode', $relfilenode_corrupted
],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data for single relfilenode on tablespace $tablespace"
@@ -71,6 +72,7 @@ sub check_relation_corruption
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data on tablespace $tablespace");
@@ -203,6 +205,7 @@ sub fail_corrupt
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/^$/],
[qr/could not read block 0 in file.*$file\":/],
"fails for corrupted data in $file");
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
index 3b63ad230f..ebe9b80a52 100644
--- a/src/bin/pg_controldata/t/001_pg_controldata.pl
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -33,6 +33,7 @@ close $fh;
command_checks_all(
[ 'pg_controldata', $node->data_dir ],
0,
+ '',
[
qr/WARNING: Calculated CRC checksum does not match value stored in file/,
qr/WARNING: invalid WAL segment size/
diff --git a/src/bin/pg_resetwal/t/002_corrupted.pl b/src/bin/pg_resetwal/t/002_corrupted.pl
index f9940d7fc5..1990669d26 100644
--- a/src/bin/pg_resetwal/t/002_corrupted.pl
+++ b/src/bin/pg_resetwal/t/002_corrupted.pl
@@ -30,6 +30,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/
@@ -46,6 +47,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index b82d3f65c4..01010914fe 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -50,7 +50,7 @@ sub pgbench
push @cmd, @args;
- $node->command_checks_all(\@cmd, $stat, $out, $err, $name);
+ $node->command_checks_all(\@cmd, $stat, '', $out, $err, $name);
# cleanup?
#unlink @filenames or die "cannot unlink files (@filenames): $!";
diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl
index f7fa18418b..b58f3548c3 100644
--- a/src/bin/pgbench/t/002_pgbench_no_server.pl
+++ b/src/bin/pgbench/t/002_pgbench_no_server.pl
@@ -25,7 +25,7 @@ sub pgbench
my ($opts, $stat, $out, $err, $name) = @_;
print STDERR "opts=$opts, stat=$stat, out=$out, err=$err, name=$name";
command_checks_all([ 'pgbench', split(/\s+/, $opts) ],
- $stat, $out, $err, $name);
+ $stat, '', $out, $err, $name);
return;
}
@@ -52,7 +52,7 @@ sub pgbench_scripts
push @cmd, '-f', $filename;
}
}
- command_checks_all(\@cmd, $stat, $out, $err, $name);
+ command_checks_all(\@cmd, $stat, '', $out, $err, $name);
return;
}
diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore
index c2862b12d6..d324c1c1fa 100644
--- a/src/bin/psql/.gitignore
+++ b/src/bin/psql/.gitignore
@@ -3,3 +3,4 @@
/sql_help.c
/psql
+/tmp_check
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 69bb297fe7..9473ab01cb 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -60,8 +60,15 @@ uninstall:
clean distclean:
rm -f psql$(X) $(OBJS) lex.backup
+ rm -rf tmp_check
# files removed here are supposed to be in the distribution tarball,
# so do not clean them in the clean/distclean rules
maintainer-clean: distclean
rm -f sql_help.h sql_help.c psqlscanslash.c
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/psql/t/001_psql.pl b/src/bin/psql/t/001_psql.pl
new file mode 100644
index 0000000000..6f2dec641b
--- /dev/null
+++ b/src/bin/psql/t/001_psql.pl
@@ -0,0 +1,879 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+my $node = get_new_node('main');
+$node->init();
+$node->start();
+
+# create a file under the test directory
+# return its full path
+sub create_test_file
+{
+ my ($fname, $contents) = @_;
+ my $fn = $node->basedir . '/' . $fname;
+ #ok(not -e $fn, "$fn must not already exists");
+ append_to_file($fn, $contents);
+ return $fn;
+}
+
+# invoke psql
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected exit status
+# - in: input stream
+# - out: list of re to check on stdout
+# - err: list of re to check on stderr
+# - name: of the test
+# - more: more raw arguments
+sub psql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $in, $out, $err, $name, @more) = @_;
+ $opts =~ s/^!// or push @more, '-X';
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+ $node->command_checks_all(\@cmd, $stat, $in, $out, $err, $name);
+ return;
+}
+
+# invoke psql interactively, making it believe it is connected to a tty
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected final exit status
+# - timeout: for interactions, in second
+# - inout: list of input expected re
+# - name: of the test
+# - remove: tell to remove init/start/end checks
+sub ipsql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $timeout, $inout, $name, $remove) = @_;
+
+ # build command to run
+ my @more = ();
+ @more = ('-X') unless $opts =~ /^!/;
+ $opts =~ s/^!//;
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+
+ # check it is running at start and end, and exit cleanly
+ unless ($remove)
+ {
+ unshift @$inout, [ "\\echo STARTING :VERSION_NUM\n", qr/STARTING \d+.*postgres=\# /s ];
+ push @$inout, [ "\\echo ENDING :VERSION_NUM\\q\n", qr/ENDING \d+$/s ];
+ }
+
+ # call the stuff
+ $node->icommand_checks(
+ \@cmd, $stat, $timeout,
+ $remove ? undef : qr/psql .*Type "help" for help..*postgres=# /s,
+ $inout, $name);
+ return;
+}
+
+#
+# check whether we have readline/history capability
+#
+my ($out, $err);
+$node->psql('postgres', "\\s\n", 'stdout' => \$out, 'stderr' => \$err);
+my $has_history = $err !~ /history is not supported by this installation/;
+
+my $EMPTY = [ qr/^$/ ];
+
+#
+# check various options
+#
+# this cannot be tested from within a SQL script
+psql('!--help', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql --help');
+psql('!-?', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql -?');
+psql('!--version', 0, '',
+ [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql --version');
+psql('--help=variables', 0, '',
+ [ qr{VERSION_NAME}, qr{unicode_border_linestyle}, qr{PSQL_EDITOR} ],
+ $EMPTY, 'psql --help=variables');
+# look for plenty of backslash-commands
+psql('--help=commands', 0, '',
+ [ qr{\\copyright}, qr{\\pset}, qr{(?!VERSION_NAME)} ],
+ $EMPTY, 'psql --help=commands');
+
+# tested elsewhere: -a -A -d -e -h -L
+psql('-b', 0, "SELECT 1/0;\n",
+ $EMPTY, [ qr{STATEMENT: SELECT 1/0}, qr{ERROR: division by zero} ], 'psql -b');
+psql('-c \\q', 0, '', $EMPTY, $EMPTY, 'psql -c');
+psql('-c \\l -c \\l', 0, '', [ qr{template0.*template0}s ], $EMPTY, 'psql -c -c');
+psql('', 0, '', [ qr{(?!1234)}, qr{5678}s ], $EMPTY, 'psql -c ;;', ('-c', 'SELECT 1234; SELECT 5678;'));
+psql('-E', 0, "\\l\n", [ qr{\* QUERY \*}, qr{template0} ], $EMPTY, 'psql -E');
+psql('-A -F _', 0, "SELECT 54, 32;\n", [ qr{54_32} ], $EMPTY, 'psql -F _');
+psql('-H', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -H');
+psql('-l', 0, '', [ qr{template0}, qr{template1} ], $EMPTY, 'psql -l');
+psql('-n', 0, "\\q\n", $EMPTY, $EMPTY, 'psql -n');
+psql('-P format=html', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -P format=html');
+psql('-P tuples_only', 0, "\\pset\n", [ qr{tuples_only.*on} ], $EMPTY, 'psql -P tuples_only');
+psql('-A -R_', 0, "VALUES (1), (2);\n", [ qr{_1_2_} ], $EMPTY, 'psql -R _');
+psql('-s', 0, "SELECT 1;\n", [ qr{verify command}, qr{SELECT 1}, qr{press return to proceed} ], $EMPTY, 'psql -s');
+psql('-S', 0, "SELECT 1 AS one\nSELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], $EMPTY, 'psql -S');
+psql('-H -T width=100', 0, "SELECT 1 AS one;\n", [ qr{table.*width=100} ], $EMPTY, 'psql -T ...');
+psql('-X -V', 0, '', [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql -X -V');
+psql('', 0, "\\echo :VERSION_NUM\n", [ qr{\d{6}} ], $EMPTY, 'psql -- show VERSION_NUM');
+psql('-v VERSION_NUM', 0, "\\echo :VERSION_NUM\n", [ qr{:VERSION_NUM} ], $EMPTY, 'psql -v ...');
+psql('-v VERSION_NUM=bla', 0, "\\echo :VERSION_NUM\n", [ qr{bla} ], $EMPTY, 'psql -v ...=.');
+psql('-w', 0, '', $EMPTY, $EMPTY, 'psql -w');
+# nope, interacts on tty
+#psql('-W', 0, "foo\n", [ qr{^$} ], [ qr{^$} ], 'psql -W');
+psql('-x', 0, "SELECT 1 AS one, 2 AS two;\n", [ qr{one \| 1.*two \| 2}s ], $EMPTY, 'psql -x');
+# some issue, \0 is not welcome somewhere
+#psql('-A -z', "SELECT 1 AS one, 2 AS two;\n", [ qr{one.two}s, qr{1.2}s ], $EMPTY, 'psql -z');
+#psql('-A -0', "SELECT 1 AS one, 2 AS two;\n", [ qr{two.1}s ], $EMPTY, 'psql -0');
+psql('-1', 0, "SELECT 54;\nSELECT 32;\n", [ qr{54}, qr{32} ], $EMPTY, 'psql -1');
+psql('-X -?', 0, '',
+ [ qr{interactive terminal}, qr{Usage:}, qr{Connection options:} ], $EMPTY, 'psql -X -?');
+psql('--csv', 0, "SELECT 1 AS one, 2 AS two\n", [ qr{one,two}, qr{1,2} ], $EMPTY, 'psql --csv');
+
+#
+# create some objects to help \d tests later
+#
+psql('', 0, '-- create some objects
+CREATE USER regress_psql_tap_1;
+ALTER ROLE regress_psql_tap_1 SET enable_indexscan TO off;
+CREATE TABLE regress_psql_tap_1_t1(data TEXT NOT NULL);
+CREATE FUNCTION regress_psql_tap_1_f1() RETURNS INTEGER AS $$ SELECT 54321 $$ LANGUAGE SQL;
+CREATE VIEW regress_psql_tap_1_v1 AS SELECT 54321 AS one;
+', [ qr{CREATE ROLE}, qr{ALTER ROLE} ], [ qr{^$} ], 'psql -- create stuff');
+
+#
+# check interactive help
+#
+# this is mostly for coverage, including occasional negatives.
+# we certainly do not want to have an output file with the whole doc.
+my @backslash_help = (
+ # \h command, list of stdout matches
+ [ 'no-help', [ qr{No help available for} ] ],
+ [ '' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ [ ' ' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ # lists
+ [ 'ALTER', [ qr{AGGREGATE}, qr{ROLE}, qr{VIEW}, qr{(?!ACCESS METHOD)}, qr{(?!TRANSFORM)} ] ],
+ [ 'ALTER TEXT SEARCH', [ qr{CONFIGURATION}, qr{DICTIONARY}, qr{PARSER}, qr{TEMPLATE} ] ],
+ [ 'ALTER FOREIGN', [ qr{DATA WRAPPER}, qr{TABLE} ] ],
+ [ 'CREATE', [ qr{ACCESS METHOD}, qr{TRANSFORM} ] ],
+ [ 'DROP', [ qr{ACCESS METHOD}, qr{VIEW} ] ],
+ # details
+ [ 'ABORT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'ALTER AGGREGATE', [ qr{RENAME TO}, qr{(?!CONVERSION)} ] ],
+ [ 'ALTER CONVERSION', [ qr{SET SCHEMA}, qr{(?!AGGREGATE)} ] ],
+ [ 'ALTER COLLATION', [ qr{REFRESH VERSION} ] ],
+ [ 'ALTER DATABASE', [ qr{CONNECTION LIMIT} ] ],
+ [ 'ALTER DEFAULT PRIVILEGES', [ qr{GRANT} ] ],
+ [ 'ALTER DOMAIN', [ qr{VALIDATE CONSTRAINT} ] ],
+ [ 'ALTER EVENT TRIGGER', [ qr{DISABLE} ] ],
+ [ 'ALTER EXTENSION', [ qr{OPERATOR FAMILY} ] ],
+ [ 'ALTER FOREIGN DATA WRAPPER', [ qr{VALIDATOR} ] ],
+ [ 'ALTER FOREIGN TABLE', [ qr{SET STORAGE} ] ],
+ [ 'ALTER FUNCTION', [ qr{PARALLEL} ] ],
+ [ 'ALTER GROUP', [ qr{DROP USER} ] ],
+ [ 'ALTER INDEX', [ qr{ALL IN TABLESPACE} ] ],
+ [ 'ALTER LANGUAGE', [ qr{PROCEDURAL}, qr{(?!INDEX)} ] ],
+ [ 'ALTER LARGE OBJECT', [ qr{OWNER TO} ] ],
+ [ 'ALTER MATERIALIZED VIEW', [ qr{SET TABLESPACE} ] ],
+ [ 'ALTER OPERATOR', [ qr{JOIN =} ] ],
+ [ 'ALTER OPERATOR CLASS', [ qr{SET SCHEMA}, qr{(?!LANGUAGE)} ] ],
+ [ 'ALTER OPERATOR FAMILY', [ qr{FUNCTION} ] ],
+ [ 'ALTER POLICY', [ qr{WITH CHECK} ] ],
+ [ 'ALTER PROCEDURE', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER PUBLICATION', [ qr{DROP TABLE} ] ],
+ [ 'ALTER ROLE', [ qr{BYPASSRLS} ] ],
+ [ 'ALTER ROUTINE', [ qr{LEAKPROOF} ] ],
+ [ 'ALTER RULE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER SCHEMA', [ qr{OWNER TO} ] ],
+ [ 'ALTER SEQUENCE', [ qr{NO MAXVALUE} ] ],
+ [ 'ALTER SERVER', [ qr{VERSION} ] ],
+ [ 'ALTER STATISTICS', [ qr{SET SCHEMA} ] ],
+ [ 'ALTER SUBSCRIPTION', [ qr{REFRESH PUBLICATION} ] ],
+ [ 'ALTER SYSTEM', [ qr{RESET ALL} ] ],
+ [ 'ALTER TABLE', [ qr{ALL IN TABLESPACE}, qr{DISABLE ROW LEVEL SECURITY} ] ],
+ [ 'ALTER TABLESPACE', [ qr{RESET} ] ],
+ [ 'ALTER TEXT SEARCH CONFIGURATION', [ qr{ALTER MAPPING FOR} ] ],
+ [ 'ALTER TEXT SEARCH DICTIONARY', [ qr{OWNER TO} ] ],
+ [ 'ALTER TEXT SEARCH PARSER', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TEXT SEARCH TEMPLATE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TRIGGER', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER TYPE', [ qr{RENAME ATTRIBUTE} ] ],
+ [ 'ALTER USER', [ qr{NOREPLICATION}, qr{(?!MAPPING)} ] ],
+ [ 'ALTER USER MAPPING', [ qr{SERVER} ] ],
+ [ 'ALTER VIEW', [ qr{DROP DEFAULT} ] ],
+ [ 'ANALYZE', [ qr{VERBOSE}, qr{SKIP_LOCKED} ] ],
+ [ 'BEGIN', [ qr{ISOLATION LEVEL} ] ],
+ [ 'CALL', [] ],
+ [ 'CHECKPOINT', [] ],
+ [ 'CLOSE', [ qr{ALL} ] ],
+ [ 'CLUSTER', [ qr{VERBOSE} ] ],
+ [ 'COMMENT', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'COMMIT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'COMMIT PREPARED', [] ],
+ [ 'COPY', [ qr{FORMAT}, qr{FORCE_NOT_NULL} ] ],
+ [ 'CREATE ACCESS METHOD', [ qr{HANDLER} ] ],
+ [ 'CREATE AGGREGATE', [ qr{SSPACE}, qr{SORTOP}, qr{FINALFUNC} ] ],
+ [ 'CREATE CAST', [ qr{AS IMPLICIT} ] ],
+ [ 'CREATE COLLATION', [ qr{LC_CTYPE}, qr{DETERMINISTIC} ] ],
+ [ 'CREATE CONVERSION', [ qr{FOR} ] ],
+ [ 'CREATE DATABASE', [ qr{ALLOW_CONNECTIONS} ] ],
+ [ 'CREATE DOMAIN', [ qr{COLLATE} ] ],
+ [ 'CREATE EVENT TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE EXTENSION', [ qr{FROM} ] ],
+ [ 'CREATE FOREIGN DATA WRAPPER', [ qr{NO VALIDATOR} ] ],
+ [ 'CREATE FOREIGN TABLE', [ qr{SERVER} ] ],
+ [ 'CREATE FUNCTION', [ qr{CALLED ON NULL INPUT}, qr{(?!PROCEDURE)} ] ],
+ [ 'CREATE GROUP', [ qr{ADMIN} ] ],
+ [ 'CREATE INDEX', [ qr{CONCURRENTLY}, qr{WHERE} ] ],
+ [ 'CREATE LANGUAGE', [ qr{TRUSTED} ] ],
+ [ 'CREATE MATERIALIZED VIEW', [ qr{DATA} ] ],
+ [ 'CREATE OPERATOR', [ qr{LEFTARG} ] ],
+ [ 'CREATE OPERATOR CLASS', [ qr{FOR ORDER BY} ] ],
+ [ 'CREATE OPERATOR FAMILY', [ qr{USING} ] ],
+ [ 'CREATE POLICY', [ qr{PERMISSIVE} ] ],
+ [ 'CREATE PROCEDURE', [ qr{SECURITY INVOKER}, qr{(?!FUNCTION)} ] ],
+ [ 'CREATE PUBLICATION', [ qr{FOR ALL TABLES} ] ],
+ [ 'CREATE ROLE', [ qr{SYSID} ] ],
+ [ 'CREATE RULE', [ qr{ALSO} ] ],
+ [ 'CREATE SCHEMA', [ qr{AUTHORIZATION} ] ],
+ [ 'CREATE SEQUENCE', [ qr{NO MINVALUE} ] ],
+ [ 'CREATE SERVER', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'CREATE STATISTICS', [ qr{FROM} ] ],
+ [ 'CREATE SUBSCRIPTION', [ qr{CONNECTION} ] ],
+ [ 'CREATE TABLE', [ qr{PARTITION BY} ] ],
+ [ 'CREATE TABLE AS', [ qr{ON COMMIT} ] ],
+ [ 'CREATE TABLESPACE', [ qr{LOCATION} ] ],
+ [ 'CREATE TEXT SEARCH CONFIGURATION', [ qr{PARSER} ] ],
+ [ 'CREATE TEXT SEARCH DICTIONARY', [ qr{TEMPLATE} ] ],
+ [ 'CREATE TEXT SEARCH PARSER', [ qr{GETTOKEN} ] ],
+ [ 'CREATE TEXT SEARCH TEMPLATE', [ qr{LEXIZE} ] ],
+ [ 'CREATE TRANSFORM', [ qr{FROM SQL WITH FUNCTION} ] ],
+ [ 'CREATE TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE TYPE', [ qr{SUBTYPE} ] ],
+ [ 'CREATE USER', [ qr{PASSWORD NULL} ] ],
+ [ 'CREATE USER MAPPING', [ qr{SERVER} ] ],
+ [ 'CREATE VIEW', [ qr{CASCADED} ] ],
+ [ 'DEALLOCATE', [ qr{PREPARE} ] ],
+ [ 'DECLARE', [ qr{INSENSITIVE} ] ],
+ [ 'DELETE', [ qr{WHERE CURRENT OF} ] ],
+ [ 'DISCARD', [ qr{PLANS} ] ],
+ [ 'DO', [ qr{LANGUAGE} ] ],
+ [ 'DROP ACCESS METHOD', [ qr{CASCADE} ] ],
+ [ 'DROP AGGREGATE', [ qr{ORDER BY} ] ],
+ [ 'DROP CAST', [ qr{RESTRICT} ] ],
+ [ 'DROP COLLATION', [ qr{RESTRICT} ] ],
+ [ 'DROP CONVERSION', [ qr{RESTRICT} ] ],
+ [ 'DROP DATABASE', [ qr{IF EXISTS} ] ],
+ [ 'DROP DOMAIN', [ qr{RESTRICT} ] ],
+ [ 'DROP EVENT TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP EXTENSION', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN DATA WRAPPER', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP FUNCTION', [ qr{RESTRICT} ] ],
+ [ 'DROP GROUP', [ qr{IF EXISTS} ] ],
+ [ 'DROP INDEX', [ qr{RESTRICT} ] ],
+ [ 'DROP LANGUAGE', [ qr{RESTRICT} ] ],
+ [ 'DROP MATERIALIZED VIEW', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR CLASS', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR FAMILY', [ qr{RESTRICT} ] ],
+ [ 'DROP OWNED', [ qr{CURRENT_USER} ] ],
+ [ 'DROP POLICY', [ qr{RESTRICT} ] ],
+ [ 'DROP PROCEDURE', [ qr{RESTRICT} ] ],
+ [ 'DROP PUBLICATION', [ qr{RESTRICT} ] ],
+ [ 'DROP ROLE', [ qr{IF EXISTS} ] ],
+ [ 'DROP ROUTINE', [ qr{RESTRICT} ] ],
+ [ 'DROP RULE', [ qr{RESTRICT} ] ],
+ [ 'DROP SCHEMA', [ qr{RESTRICT} ] ],
+ [ 'DROP SEQUENCE', [ qr{RESTRICT} ] ],
+ [ 'DROP SERVER', [ qr{RESTRICT} ] ],
+ [ 'DROP STATISTICS', [ qr{IF EXISTS} ] ],
+ [ 'DROP SUBSCRIPTION', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLESPACE', [ qr{IF EXISTS} ] ],
+ [ 'DROP TEXT SEARCH CONFIGURATION', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH DICTIONARY', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH PARSER', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH TEMPLATE', [ qr{RESTRICT} ] ],
+ [ 'DROP TRANSFORM', [ qr{LANGUAGE} ] ],
+ [ 'DROP TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP TYPE', [ qr{RESTRICT} ] ],
+ [ 'DROP USER', [ qr{IF EXISTS} ] ],
+ [ 'DROP USER MAPPING', [ qr{SERVER} ] ],
+ [ 'DROP VIEW', [ qr{RESTRICT} ] ],
+ [ 'END', [ qr{TRANSACTION}, qr{CHAIN}, qr{(?!COMMIT)} ] ],
+ [ 'EXECUTE', [ ] ],
+ [ 'EXPLAIN', [ qr{VERBOSE} ] ],
+ [ 'FETCH', [ qr{RELATIVE} ] ],
+ [ 'GRANT', [ qr{ALL TABLES} ] ],
+ [ 'IMPORT FOREIGN SCHEMA', [ qr{FROM SERVER} ] ],
+ [ 'INSERT', [ qr{ON CONFLICT} ] ],
+ [ 'LISTEN', [ ] ],
+ [ 'LOAD', [ ] ],
+ [ 'LOCK', [ qr{NOWAIT} ] ],
+ [ 'MOVE', [ qr{FORWARD} ] ],
+ [ 'NOTIFY', [ ] ],
+ [ 'PREPARE', [ qr{AS} ] ],
+ [ 'PREPARE TRANSACTION', [ ] ],
+ [ 'REASSIGN OWNED', [ qr{SESSION_USER} ] ],
+ [ 'REFRESH MATERIALIZED VIEW', [ qr{CONCURRENTLY} ] ],
+ [ 'REINDEX', [ qr{DATABASE} ] ],
+ [ 'RELEASE', [ qr{SAVEPOINT} ] ],
+ [ 'RESET', [ qr{RESET ALL} ] ],
+ [ 'REVOKE', [ qr{PRIVILEGES} ] ],
+ [ 'ROLLBACK', [ qr{CHAIN} ] ],
+ [ 'ROLLBACK PREPARED', [ ]],
+ [ 'SAVEPOINT', [ ] ],
+ [ 'SECURITY LABEL', [ qr{SUBSCRIPTION} ] ],
+ [ 'SELECT', [ qr{RECURSIVE} ] ],
+ [ 'SET', [ qr{SESSION} ] ],
+ [ 'SET CONSTRAINTS', [ qr{IMMEDIATE} ] ],
+ [ 'SET ROLE', [ qr{ROLE NONE} ] ],
+ [ 'SET SESSION AUTHORIZATION', [ qr{DEFAULT} ] ],
+ [ 'SET TRANSACTION', [ qr{SNAPSHOT} ] ],
+ [ 'SHOW', [ qr{SHOW ALL} ] ],
+ [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ],
+ [ 'TABLE', [ qr{ONLY} ] ], # hmmm...
+ [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ],
+ [ 'UNLISTEN', [ ] ],
+ [ 'UPDATE', [ qr{RETURNING} ] ],
+ [ 'VACUUM', [ qr{FREEZE} ] ],
+ [ 'VALUES', [ qr{ORDER BY} ] ],
+ [ 'WITH', [ qr{RECURSIVE} ] ], # SELECT duplicate?
+);
+
+for my $h (@backslash_help)
+{
+ my ($cmd, $out) = @$h;
+ push @$out, qr{$cmd};
+ psql('', 0, "\\h $cmd\n", $out, [ qr{^$} ], "psql -- \\h $cmd");
+}
+
+# special cases
+psql('', 0, "\\h ROLLBACK TO SAVEPOINT\n", [ qr{ROLLBACK}, qr{SAVEPOINT} ],
+ [ qr{^$} ], "psql -- \\h ROLLBACK TO SAVEPOINT");
+psql('', 0, "\\h SELECT INTO\n", [ qr{SELECT}, qr{INTO} ],
+ [ qr{^$} ], "psql -- \\h SELECT INTO");
+
+#
+# check describe and other backslash commands
+#
+# the output can vary significantly, especially with +
+my @backslash_out = (
+ # empty
+ [ "\\d\n", [ qr{List of relations}, qr{regress_psql_tap_1_t1}, qr{(?!pg_locks)} ] ],
+ [ "\\dS\n", [ qr{List of relations}, qr{pg_locks} ] ],
+ # aggregates
+ [ "\\da\n", [ qr{List of aggregate functions}, qr{(?!Description)} ] ],
+ [ "\\da+\n", [ qr{List of aggregate functions}, qr{(Description)} ] ],
+ [ "\\daS\n", [ qr{array_agg}, qr{avg}, qr{bit_and}, qr{bit_or}, qr{count}, qr{max} ] ],
+ # access methods
+ [ "\\dA\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{(?!Description)} ] ],
+ [ "\\dA+\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{Description} ] ],
+ # tablespaces
+ [ "\\db\n", [ qr{List of tablespaces}, qr{pg_default}, qr{pg_global}, qr{(?!Size)} ] ],
+ [ "\\db+ pg_def*\n", [ qr{List of tablespaces}, qr{pg_default}, qr{(?!pg_global)}, qr{Size} ] ],
+ # functions
+ [ "\\df\n", [ qr{List of functions} ] ],
+ [ "\\df+\n", [ qr{List of functions} ] ],
+ [ "\\dftS\n", [ qr{RI_}, qr{Type}, qr{(?!Volatility)}, qr{(?!Parallel)} ] ],
+ [ "\\dftS+\n", [ qr{Volatility}, qr{Parallel} ] ],
+ [ "\\dfwS\n", [ qr{lag}, qr{dense_rank}, qr{(?!Owner)}, qr{(?!Language)} ] ],
+ [ "\\dfwS+\n", [ qr{cume_dist}, qr{Owner}, qr{Language} ] ],
+ [ "\\dfnS\n", [ qr{abbrev}, qr{(?!bit_or)} ] ],
+ [ "\\dfaS\n", [ qr{bit_and}, qr{(?!abs)} ] ],
+ [ "\\dfpS\n", [ qr{List of functions} ] ],
+ [ "\\dfwtS\n", [ qr{RI_FKey_}, qr{last_value}, qr{nth_value}, qr{(?!bit_and)} ] ],
+ # type
+ [ "\\dTS charac*\n", [ qr{List of data types}, qr{character varying}, qr{(?!double)}, qr{(?!Size)} ] ],
+ [ "\\dTS+\n", [ qr{aclitem}, qr{Size} ] ],
+ # operator
+ [ "\\doS\n", [ qr{List of operators}, qr{!~\*} ] ],
+ [ "\\doS+\n", [ qr{<->}, qr{Result type}] ],
+ # databases
+ [ "\\l\n", [ qr{List of databases}, qr{template0}, qr{template1}, qr{(?!Size)} ] ],
+ [ "\\l a*\n", [ qr{(?!template[01])} ] ],
+ [ "\\l+\n", [ qr{Size}, qr{Encoding} ] ],
+ [ "\\list\n", [ qr{List of databases} ] ],
+ # permissions: well tested
+ # default acls (empty)
+ [ "\\ddp\n", [ qr{Default access privileges} ] ],
+ # descriptions (empty)
+ [ "\\dd\n", [ qr{Object descriptions} ] ],
+ # tables: well tested
+ # roles/users/groups
+ [ "\\du\n", [ qr{Superuser}, qr{regress_psql_tap_1}, qr{List of roles}, qr{(?!Description)} ] ],
+ [ "\\du+\n", [ qr{Superuser}, qr{List of roles}, qr{Description} ] ],
+ [ "\\dg\n", [ qr{Superuser}, qr{List of roles} ] ],
+ # role settings
+ [ "\\drds\n", [ qr{List of settings} ] ],
+ [ "\\drds regress_* *\n", [ qr{List of settings} ] ],
+ # index/...
+ [ "\\diS\n", [ qr{List of relations}, qr{pg_am_oid_index} ] ],
+ [ "\\diS+\n", [ qr{Table} ] ],
+ # partition tables: well tested
+ # large objects
+ [ "\\dl", [ qr{Large objects} ] ],
+ # languages
+ [ "\\dL\n", [ qr{List of languages}, qr{plpgsql}, qr{(?!Trusted)} ] ],
+ [ "\\dL+\n", [ qr{plpgsql}, qr{Trusted} ] ],
+ # domains
+ [ "\\dD\n", [ qr{List of domains}, qr{(?!Access privileges)} ] ],
+ [ "\\dD+\n", [ qr{List of domains}, qr{Access privileges} ] ],
+ # conversions
+ [ "\\dcS\n", [ qr{List of conversions}, qr{UTF8}, qr{(?!Description)} ] ],
+ [ "\\dcS+\n", [ qr{List of conversions}, qr{LATIN1}, qr{Description} ] ],
+ # event triggers (empty)
+ [ "\\dy\n", [ qr{List of event triggers}, qr{(?!Description)} ] ],
+ [ "\\dy+\n", [ qr{List of event triggers}, qr{Description} ] ],
+ # casts
+ [ "\\dC\n", [ qr{List of casts}, qr{timestamp without time zone}, qr{(?!Description)} ] ],
+ [ "\\dC+\n", [ qr{Function}, qr{Implicit}, qr{Description} ] ],
+ # collations
+ [ "\\dOS\n", [ qr{List of collations}, qr{Deterministic}, qr{(?!Description)} ] ],
+ [ "\\dOS+\n", [ qr{POSIX}, qr{Description} ] ],
+ # schemas
+ [ "\\dn\n", [ qr{List of schemas}, qr{Owner}, qr{(?!Description)} ] ],
+ [ "\\dn+\n", [ qr{Access privileges}, qr{Description} ] ],
+ # text search misc.
+ [ "\\dFp\n", [ qr{List of text search parsers}, qr{default} ] ],
+ [ "\\dFp+\n", [ qr{hword}, qr{numword} ] ],
+ [ "\\dFd\n", [ qr{List of text search dictionaries}, qr{simple} ] ],
+ [ "\\dFd+\n", [ qr{Init options}, qr{Description} ] ],
+ [ "\\dFt\n", [ qr{List of text search templates}, qr{simple} ] ],
+ [ "\\dFt+\n", [ qr{Lexize}, qr{Description} ] ],
+ [ "\\dF\n", [ qr{List of text search configurations}, qr{simple} ] ],
+ [ "\\dF+\n", [ qr{email}, qr{url_path} ] ],
+ # extensions
+ [ "\\dx\n", [ qr{List of installed extensions}, qr{Version}, qr{plpgsql} ] ],
+ [ "\\dx+\n", [ qr{Objects in extension}, qr{plpgsql_call_handler} ] ],
+ # other backslash commands
+ [ "\\f\n", [ qr{Field separator is} ] ],
+ [ "\\H\\H\n", [ qr{Output format is html.* is aligned}s ] ],
+ [ "\\\?\n", [ qr{General}, qr{Help}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\C foobar\n", [ qr{Title is "foobar"} ] ],
+ [ "\\cd /\n\\cd\n", [ qr{^$} ] ],
+ [ "\\conninfo\n", [ qr{You are connected to database} ] ],
+ [ "\\copy (SELECT 5432 UNION SELECT 2345 ORDER BY 1) TO STDOUT\n", [ qr/\b2345\b.*\b5432\b/s ] ],
+ [ "\\copyright\n", [ qr/The Regents of the University of California/ ] ],
+ [ "\\encoding\n", [ qr/./ ] ],
+ [ "\\encoding :ENCODING\n", $EMPTY ],
+ [ "\\errverbose\n", [ qr{There is no previous error} ] ],
+ [ "\\lo_list\n", [ qr{Large objects} ] ],
+ [ "\\if true\\q\\endif\n", $EMPTY ],
+ # ???
+ #[ "SELECT md5('hello world');\n\\s\n", [ qr{5eb63bbbe0}, qr{SELECT md5} ] ],
+ [ "\\set\n", [ qr{ENCODING = }, qr{VERSION_NUM = } ] ],
+ [ "\\set COMP_KEYWORD_CASE preserve-lower\n\\set COMP_KEYWORD_CASE lower\n" .
+ "\\set COMP_KEYWORD_CASE upper\n\\echo :COMP_KEYWORD_CASE", [ qr/upper/ ] ],
+ [ "\\set HISTCONTROL ignorespace\n\\set HISTCONTROL ignoredups\n" .
+ "\\set HISTCONTROL ignoreboth\n\\echo :HISTCONTROL", [ qr/ignoreboth/ ] ],
+ [ "\\set ECHO_HIDDEN on\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/template0/ ] ],
+ [ "\\set ECHO_HIDDEN noexec\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/(?!template0)/ ] ],
+ [ "\\set LAST_ERROR_MESSAGE foo bla\n\\echo :LAST_ERROR_MESSAGE\n", [ qr{foobla} ] ],
+ [ "\\set AUTOCOMMIT off\n\\set ON_ERROR_ROLLBACK on\nSELECT 5432 AS pg;\nSELECT 2345 AS pg;\n",
+ [ qr{5432}, qr{2345} ] ],
+ [ "\\set ON_ERROR_ROLLBACK interactive\n\\echo :ON_ERROR_ROLLBACK\n", [ qr{interactive} ] ],
+ [ "\\set VERBOSITY verbose\n\\echo :VERBOSITY\n", [ qr{verbose} ] ],
+ [ "\\set foo bla\n\\echo :foo\\unset foo\\echo :foo\n", [ qr{^bla.*:foo}s ] ],
+ [ "\\set foo bla\n\\echo :'foo' :\"foo\"\n", [ qr{'bla'}, qr{"bla"} ] ],
+ [ "\\setenv FOO bla\n\\setenv FOO\n", [ qr{^$} ] ],
+ [ "\\timing\nSELECT 1;\n\\timing\n", [ qr{Timing is on.*Timing is off}s, qr(Time: \d+\.\d{3} ms) ] ],
+ [ "\\timing ON\n\\timing OFF\n", [ qr{Timing is on.*Timing is off}s ] ],
+ [ "\\T foo\n", [ qr{Table attributes are "foo"} ] ],
+ [ "\\T\n", [ qr{Table attributes unset} ] ],
+ # help variants
+ [ "\\?\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? commands\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? bad\n", [ qr{General} ] ],
+ [ "\\? variables\n",
+ [ qr{psql variables:}, qr{Display settings:}, qr{Environment variables} ] ],
+ [ "\\? options\n",
+ [ qr{interactive terminal}, qr{Report bugs} ] ],
+ # pset format
+ [ "\\pset format aligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is aligned}, qr{ 1 \| 2} ] ],
+ [ "\\pset format asciidoc\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is asciidoc}, qr{\|1 \|2} ] ],
+ [ "\\pset format csv\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is csv}, qr{1,2} ] ],
+ [ "\\pset format html\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is html}, qr{<td align="right">1</td>} ] ],
+ [ "\\pset format latex\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex}, qr{1 \& 2} ] ],
+ [ "\\pset format latex-longtable\n\\pset format\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex-longtable}, qr{\\raggedright\{1\}} ] ],
+ [ "\\pset format troff-ms\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is troff-ms}, qr{1\t2} ] ],
+ [ "\\pset format unaligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is unaligned}, qr{1\|2} ] ],
+ [ "\\pset format wrapped\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is wrapped}, qr{ 1 \| 2} ] ],
+ [ "\\pset format\n", [ qr{Output format is aligned} ] ],
+ # pset linestyle
+ [ "\\pset linestyle ascii\n", [ qr{Line style is ascii} ] ],
+ [ "\\pset linestyle old-ascii\n", [ qr{Line style is old-ascii} ] ],
+ [ "\\pset linestyle unicode\n", [ qr{Line style is unicode} ] ],
+ # pset unicode_*_linestyle
+ [ "\\pset unicode_header_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_header_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_border_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_border_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_column_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_column_linestyle double\n", [ qr{"double"} ] ],
+);
+
+for my $h (@backslash_out)
+{
+ my ($in, $out) = @$h;
+ psql('', 0, $in, $out, [ qr{^$} ], "psql -- $in");
+}
+
+# test some errors
+my @backslash_err = (
+ [ "\\unknown stuff\n", [ qr/invalid command / ] ],
+ [ "\\cd /no/such/dir\n", [ qr/could not change directory to/ ] ],
+ [ "\\copy\n", [ qr/arguments required/ ] ],
+ [ "\\copy bad stuff on the line\n", [ qr/parse error at \"/ ] ],
+ [ "\\copy binary (\n", [ qr/parse error at end of line/ ] ],
+ [ "\\copy public.regress_psql_tap_1_t1(data) to '/no/such/file'", [ qr{/no/such/file} ] ],
+ [ "\\encoding no-such-encoding\n", [ qr/invalid encoding name/ ] ],
+ [ "BAD;\n\\errverbose\n", [ qr/syntax error at or near "BAD"/, qr/\b42601\b/ ] ],
+ [ "\\dfX\n", [ qr/invalid command/ ] ],
+ [ "\\dPX\n", [ qr/invalid command/ ] ],
+ [ "\\drX\n", [ qr/invalid command/ ] ],
+ [ "\\dRX\n", [ qr/invalid command/ ] ],
+ [ "\\dFX\n", [ qr/invalid command/ ] ],
+ [ "\\deX\n", [ qr/invalid command/ ] ],
+ [ "\\dX\n", [ qr/invalid command/ ] ],
+ [ "\\i\n", [ qr/missing required argument/ ] ],
+ [ "\\ir\n", [ qr/missing required argument/] ],
+ [ "\\lo_bad\n", [ qr/invalid command/ ] ],
+ [ "\\lo_export\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import /no/such/file\n", [ qr{no/such/file} ] ],
+ [ "\\lo_unlink\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_unlink -1\n", [ qr/large object 4294967295 does not exist/ ] ],
+ [ "\\setenv\n", [ qr/missing required argument/ ] ],
+ [ "\\setenv PG_REGRESS_TEST=1\n", [ qr/environment variable name must not contain "="/ ] ],
+ [ "\\unset\n", [ qr/missing required argument/ ] ],
+ [ "\\set foo \`no-such-command\`\n", [ qr/no-such-command/ ] ],
+ [ "\\set === xxx\n", [ qr/invalid variable name/ ] ],
+ [ "\\w\n", [ qr/missing required argument/ ] ],
+ [ "\\pset format bad\n", [ qr/allowed formats are aligned, asciidoc/ ] ],
+ [ "\\pset linestyle bad\n", [ qr/allowed line styles are ascii, old-ascii, unicode/ ] ],
+ [ "\\pset unicode_border_linestyle bad\n", [ qr/allowed Unicode border line styles are single, double/ ] ],
+ [ "\\pset unicode_column_linestyle bad\n", [ qr/allowed Unicode column line styles are single, double/ ] ],
+ [ "\\pset unicode_header_linestyle bad\n", [ qr/allowed Unicode header line styles are single, double/ ] ],
+ [ "COPY regress_psql_tap_1_t1 FROM STDIN \\watch 0.01\n", [ qr/watch cannot be used with COPY/ ] ],
+ [ "\\watch 0.01\n", [ qr/watch cannot be used with an empty query/ ] ],
+ # check variable constraints values
+ [ "\\set ECHO bad\n", [ qr{none, errors, queries, all} ] ],
+ [ "\\set ECHO_HIDDEN bad\n", [ qr{on, off, noexec} ] ],
+ [ "\\set ON_ERROR_ROLLBACK bad\n", [ qr{on, off, interactive} ] ],
+ [ "\\set COMP_KEYWORD_CASE bad\n", [ qr{lower, upper, preserve-lower, preserve-upper} ] ],
+ [ "\\set HISTCONTROL bad\n", [ qr{none, ignorespace, ignoredups, ignoreboth} ] ],
+ [ "\\set VERBOSITY bad\n", [ qr{default, verbose, terse, sqlstate} ] ],
+ [ "\\set SHOW_CONTEXT bad\n", [ qr{never, errors, always} ] ],
+);
+
+for my $h (@backslash_err)
+{
+ my ($in, $err) = @$h;
+ psql('-L /dev/null', 0, $in, [ qr{^$} ], $err, "psql -- $in");
+}
+
+# other errors
+
+# \timing
+psql('', 0, "\\timing bad\n",
+ [ qr{Timing is off} ], [ qr{unrecognized value} ], 'psql \timing error');
+
+# check stdout vs stderr output
+psql('', 0, "\\echo hello\n\\warn world\n\\q\n",
+ [ qr{^hello$} ], [ qr{^world$} ], 'psql in/out/err');
+
+# \watch test
+psql('', 0,
+ "CREATE TEMP SEQUENCE tmp_seq MAXVALUE 3 NO CYCLE;\n" .
+ "\\t on\n" .
+ "\\timing\n" .
+ "SELECT 'x=' || NEXTVAL('tmp_seq') \\watch 0.01\n",
+ [ qr/x=1\b.*x=2\b.*x=3\b/s, qr/(?!x=[04-9])/ ],
+ [ qr/nextval: reached maximum value of sequence/ ],
+ 'psql watch');
+
+# skip un*x-specific zone if it does not look like un*x
+goto END_UNIX_ZONE
+ unless -e "/dev/null" and -d "/tmp" and -x "/bin/cat" and -x "/bin/echo";
+
+# this probably only works under UN*X-like systems
+psql('', 0, "\\setenv PSQL_EDITOR echo\nSELECT 1 AS one;\n\\e\n\\p\n",
+ [ qr/one.*one/s, qr/SELECT 1/ ], $EMPTY, 'psql edit');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ef regress_psql_tap_1_f1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ef');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ev regress_psql_tap_1_v1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ev');
+psql('', 0, "\\setenv FOO bla\\set foo \`echo \$FOO\`\n\\echo :foo\n",
+ [ qr/bla/ ], $EMPTY, 'psql backtick');
+psql('', 0, "\\setenv FOO blabla\n\\! echo \$FOO\n",
+ [ qr/blabla/ ], $EMPTY, 'psql !');
+
+psql('-o /dev/null', 0, "SELECT 5432 AS pg\n", $EMPTY, $EMPTY, 'psql -o null');
+psql('', 0, "\\o /dev/null\nSELECT 1;\n", $EMPTY, $EMPTY, 'psql \o null');
+psql('', 0, "\\o | cat\nSELECT 5432 AS pg;\n\\o\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql \o cat');
+
+psql('', 0, "SELECT 5432 AS pg;\n\\w /dev/null\n",
+ [ qr/pg.*\b5432\b/s ], $EMPTY, 'psql \w null');
+
+psql('', 0, "SELECT 1\\g /dev/null\n",
+ $EMPTY, $EMPTY, 'psql \g null');
+psql('', 0, "SELECT 5432\\g | cat\n",
+ [ qr/\b5432\n/ ], $EMPTY, 'psql \g cat');
+
+psql('--log-file=/dev/null', 0, "SELECT 5432 AS pg\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql -L null');
+
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) FROM PROGRAM 'echo moe'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy echo');
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) TO PROGRAM 'cat'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy cat'); # :-)
+
+END_UNIX_ZONE:
+
+psql('', 0, "\\i /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \i error');
+psql('', 0, "\\i ~/there/is/no/such/file\n",
+ [ qr{^$} ], [ qr{there/is/no/such/file} ], 'psql \i tilde');
+
+# no such file
+psql('', 0, "\\o /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \o error');
+
+psql('', 0, "\\w /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \w error');
+
+psql('-L /no/such/file', 1, '',
+ [ qr{^$} ], [ qr{could not open log file} ], 'psql -L bad');
+
+# \copy errors
+psql('', 0, "\\copy regress_psql_tap_1_t1 to program '/no/such/prgm'",
+ [ qr/COPY 1/ ], [ qr{/no/such/prgm} ], 'psql copy to bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from program '/no/such/prgm'",
+ [ qr/COPY 0/ ], [ qr{/no/such/prgm} ], 'psql copy from bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 to '/tmp'",
+ $EMPTY, [ qr{directory} ], 'psql copy to tmp');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from '/tmp'",
+ $EMPTY, [ qr/directory/ ], 'psql copy to tmp');
+
+# misc errors
+psql('-P format=bad', 1, '', $EMPTY,
+ [ qr/allowed formats are aligned/, qr/could not set printing parameter/ ], 'psql -P bad');
+psql('-Q', 1, '', $EMPTY, [ qr/Try ".* --help" for more information/ ], 'psql -BAD');
+
+#
+# .psqlrc
+#
+# note: reading the system wide psqlrc cannot be avoided
+#
+#import env;
+my $psqlrc = create_test_file('regress_psql_tap_1_psqlrc', "SELECT 1 AS one\n");
+$ENV{PSQLRC} = $psqlrc;
+psql('!', 0, "SELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], [ qr{^$} ], 'psql .psqlrc');
+delete $ENV{PSQLRC};
+ok(unlink $psqlrc, "unlink $psqlrc");
+
+#
+# large objects
+#
+my $lofile = create_test_file('regress_psql_tap_1_lo', '0123456789ABCDEF' x 1024);
+my $esc_lofile = $lofile;
+$esc_lofile =~ s/'/''/g;
+psql('-H', 0,
+ "\\lo_import '$esc_lofile' 'regress psql TAP lo'\n\\lo_list\n",
+ [ qr{lo_import \d+}, qr{Large objects}, qr{regress psql TAP lo} ], [ qr{^$} ], 'psql lo 1');
+ok(unlink $lofile, "unlink $lofile (1)");
+psql('', 0,
+ "SELECT oid AS loboid\n" .
+ "FROM pg_catalog.pg_largeobject_metadata\n" .
+ "WHERE pg_catalog.obj_description(oid, 'pg_largeobject') = 'regress psql TAP lo'\\gset\n" .
+ "\\echo loboid = :loboid\n" .
+ "\\pset tuples_only on\n" .
+ "SELECT 'lo extract: ' || lo_get(:loboid, 826, 3)::TEXT AS extract;\n" .
+ "\\lo_export :loboid '/no/such/dir/stuff'\n" . # this one fails
+ "\\lo_export :loboid '$esc_lofile'\n" .
+ "\\lo_unlink :loboid\n",
+ [ qr{loboid = \d+}, qr{lo extract: \\x414243} ],
+ [ qr{could not open file "/no/such/dir/stuff"} ],
+ 'psql lo 2');
+ok(-e $lofile, "re-created $lofile");
+ok(unlink $lofile, "unlink $lofile (2)");
+# TODO
+#ok(not -e $lofile, "file $lofile is removed");
+
+#
+# INTERACTIVE STUFF
+#
+# pretty slow... espacially the tab-completion thing
+#
+# disable pager to work around pagination interactions
+
+# basic test
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ "\\echo hello world\n", qr/hello world.*postgres=\# /s ],
+ [ "help\n", qr/You are using.*\\copyright.*quit.*postgres=\# /s ],
+ [ "SELECT\nhelp\n\\r\n", qr/help or press control-[CD].*postgres=\# /s ],
+ [ "SELECT\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT (\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT \$\$\nquit\n\$\$\\r\n", qr/Use control-[CD] to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT '\nquit'\\r\n", qr/quit'.*postgres=\# /s ],
+ [ "SELECT '\n\\q\n", qr/Use control-[CD] to quit.*postgres'\# /s ],
+ [ "'\\r\n", qr/buffer reset.*postgres=\# /s ],
+ [ "SELECT 1\n\\p\n\\r\n", qr/SELECT 1.*SELECT 1.*buffer reset.*postgres=\#/s ],
+ #[ "\\prompt tmp_var\nfoobla\n\\echo hello :tmp_var\n\\unset foo\n\\echo world :tmp_var\n",
+ # qr/hello foobla.*world :tmp_var.*postgres=\# /s ],
+ [ "\\password\n", qr/Enter new password: / ],
+ [ "foo\n", qr/Enter it again: / ],
+ [ "bla\n", qr/Passwords didn't match.*postgres=\# /s ],
+ [ "exit\n" ],
+ ],
+ 'ipsql basic', 1);
+
+# signals
+
+# prompt
+ipsql('-P pager', 0, 5,
+ [ # default prompts
+ [ "SELECT 1 +\n", qr/postgres-\# / ],
+ [ "(2 * \n", qr/postgres\(\# / ],
+ [ "3) + '\n", qr/postgres\'\# / ],
+ [ "4' + \$\$\n", qr/postgres\$\# /],
+ [ "5\$\$ AS \"\n", qr/postgres\"\# / ],
+ [ "seize\" /*\n", qr/postgres\*\# / ],
+ [ " ignored */;\n", qr/\bseize\b.*\b16\b.*postgres=\# /s ],
+ [ "\\if false\n", qr/postgres\@\# / ],
+ [ "\\echo IGNORED\n", qr/command ignored.*if block.*postgres\@\# /s ],
+ # BUG: prompt with - instead of @
+ [ "SELECT 1;\n", qr/query ignored.*if block.*postgres.\# /s ],
+ [ "\\endif\n", qr/postgres=\# / ],
+ [ "\\set SINGLELINE ON\n", qr/postgres\^\# / ],
+ [ "\\set SINGLELINE OFF\n", qr/postgres=\# / ],
+ [ "COPY regress_psql_tap_1_t1(data) FROM STDIN;\n", qr/>> / ],
+ [ "hello\nworld\n\\.\n", qr/COPY 2\b.*postgres=\# /s ],
+ # set new prompts (not tested: %`)
+ [ "\\set PROMPT1 '%%%[%]%?%M%m%>%n%/%~%#%p%R%x%l%:QUIET:%040'\n", qr/%.*\d+(on|off) /s ],
+ [ "\\set PROMPT1 '1> '\n\\set PROMPT2 '2> '\n\\set PROMPT3 '3> '\n", qr/\b1> / ],
+ [ "COPY regress_psql_tap_1_t1(data)\n", qr/\b2> / ],
+ [ "FROM STDIN;\n", qr/\b3> / ],
+ [ "calvin\nhobbes\nsusie\nmoe\n\\.\n", qr/COPY 4\b.*\b1> /s ],
+ # reset prompts
+ [ "\\set PROMPT1 '%/%R%# '\n", qr/PROMPT1.*postgres=# /s ],
+ [ "\\set PROMPT2 '%/%R%# '\n", qr/PROMPT2.*postgres=# /s ],
+ [ "\\set PROMPT3 '>> '\n", qr/PROMPT3.*postgres=# /s ],
+ ],
+ 'ipsql prompt 1');
+
+goto END_HISTORY_ZONE
+ unless $has_history;
+
+#TODO: reliably check for un*x vs windows?
+my $stop = -e '/dev/null' ? "\004" : "\003";
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ $stop, qr/\\q/ ],
+ ],
+ 'ipsql signals 1', 1);
+
+psql('', 0, "\\s /dev/null\n", $EMPTY, $EMPTY, 'psql \s null');
+
+# tab-complation
+ipsql('-P pager', 0, 5,
+ [ # commands
+ [ "SEL\t", qr/SELECT / ],
+ [ "* FROM pg_catalog.pg_ca\t", qr/pg_catalog\.pg_cast / ],
+ [ "LIMIT 17;\n", qr/\b17 rows.*postgres=\# /s ],
+ # backslash commands
+ [ "\\d\t", qr/(\\da.*\\dy.*postgres=# )?\\d/s ],
+ [ "y\n", qr/List of event triggers.*postgres=\# /s ],
+ # pset
+ [ "\\pset \t", qr/(unicode_column_linestyle.*postgres=\# )?\\pset/s ],
+ [ "tu\t", qr/tuples_only / ],
+ [ "off\n", qr/postgres=\# / ],
+ [ "\\pset unicode_b\t", qr/unicode_border_linestyle / ],
+ [ "\t", qr/(double.*single.*postgres=\# )?/s ],
+ [ "double\n", qr/Unicode border line style is "double".*postgres=\# /s ],
+ # set
+ [ "\\set VERB\t", qr/VERBOSITY / ],
+ [ "\t", qr/(default.*verbose.*postgres=\# )?/s ],
+ [ "d\t\n", qr/default.*postgres=\# /s ],
+ # misc
+ [ "\\cd \t", qr/(.*postgres=\# )?\\cd /s ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # object type completion,
+ # strange: the list is not seen from the test driver
+ # but the coverage works as expected.
+ #[ "CREATE \t", qr/i(MATERIALIZED VIEW.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "CREATE \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "DROP \t", qr/(UNLOGGED.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "DROP \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "ALTER \t", qr/(TABLESPACE.*postgres=\# )/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ALTER \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ANALYZE (\t", qr/(SKIP_LOCKED.*postgres=\# )?ANALYZE \(/s ],
+ [ "V\tOF\t", qr/VERBOSE OFF/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # SET
+ [ "SET \t", qr/(zero_damaged_pages.*postgres=\# )?SET /s ],
+ [ "da\t", qr/datestyle / ],
+ [ "\t", qr/TO / ],
+ #[ "\t", qr/(YMD.*postgres=\# SET datestyle TO )?/s ],
+ [ "\tDE\t", qr/DEFAULT/ ],
+ [ ";\n", qr/SET.*postgres=\# /s ],
+ # SET completion on a GUC variable,
+ #[ "SET default_with\t", qr/default_with_oids / ],
+ #[ "TO \t", qr/(YES.*postgres=\# SET default_with_oids )?TO /s ],
+ #[ "D\t", qr/DEFAULT/ ],
+ #[ ";\n", qr/SET.*postgres=\# / ],
+ # psql variables
+ [ "\\ec\t", qr/\\echo / ],
+ [ ":q\t", qr/QUIET/ ],
+ [ ":'PO\t", qr/:'PORT'/ ],
+ [ ":\"HISTC\t", qr/:"HISTCONTROL"/ ],
+ [ "\n", qr/(on|off).*'\d+'.*"[a-z]+".*postgres=\# /s ],
+ # file
+ [ "COPY regress_psql_tap_1_t1 FROM \t", qr/(postgres=\# COPY .* FROM )?/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ ],
+ 'ipsql tab 1');
+
+END_HISTORY_ZONE:
+
+# final cleanup
+psql('', 0, '-- cleanup objects
+DROP VIEW regress_psql_tap_1_v1;
+DROP FUNCTION regress_psql_tap_1_f1;
+DROP TABLE regress_psql_tap_1_t1;
+DROP USER regress_psql_tap_1;
+', [ qr{DROP VIEW}, qr{DROP FUNCTION}, qr{DROP TABLE}, qr{DROP ROLE} ], [ qr{^$} ], 'psql -- drop stuff');
+
+$node->stop();
+done_testing();
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index b685b35282..2692c99e17 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -92,6 +92,7 @@ $node->issues_sql_like(
$node->command_checks_all(
[ 'vacuumdb', '--analyze', '--table', 'vacview', 'postgres' ],
0,
+ '',
[qr/^.*vacuuming database "postgres"/],
[qr/^WARNING.*cannot vacuum non-tables or special system tables/s],
'vacuumdb with view');
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 270bd6c856..9c28bec4a7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1676,6 +1676,28 @@ sub command_checks_all
=pod
+=item $node->icommand_checks(cmd, ...)
+
+TestLib::icommand_checks with our connection parameters.
+See command_ok(...)
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local $ENV{PGHOST} = $self->host;
+ local $ENV{PGPORT} = $self->port;
+
+ TestLib::icommand_checks(@_);
+ return;
+}
+
+=pod
+
=item $node->issues_sql_like(cmd, expected_sql, test_name)
Run a command on the node, then verify that $expected_sql appears in the
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 92199792eb..1b2f7d0cfb 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -53,6 +53,7 @@ use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
+use Expect;
# specify a recent enough version of Test::More to support the
# done_testing() function
@@ -81,6 +82,7 @@ our @EXPORT = qw(
command_like_safe
command_fails_like
command_checks_all
+ icommand_checks
$windows_os
);
@@ -792,6 +794,8 @@ Arguments:
=item C<ret>: Expected exit code
+=item C<in>: Stdin for command
+
=item C<out>: Expected stdout from command
=item C<err>: Expected stderr from command
@@ -806,12 +810,13 @@ sub command_checks_all
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($cmd, $expected_ret, $out, $err, $test_name) = @_;
+ my ($cmd, $expected_ret, $in, $out, $err, $test_name) = @_;
+ $in = '' if not defined $in;
# run command
my ($stdout, $stderr);
print("# Running: " . join(" ", @{$cmd}) . "\n");
- IPC::Run::run($cmd, '>', \$stdout, '2>', \$stderr);
+ IPC::Run::run($cmd, '<', \$in, '>', \$stdout, '2>', \$stderr);
# See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
my $ret = $?;
@@ -840,6 +845,84 @@ sub command_checks_all
=pod
+=item icommand_checks(cmd, ret, timeout, init, inout, name)
+
+Run a command and check its status and outputs.
+Arguments:
+
+=over
+
+=item C<cmd> ref to list for command, options and arguments to run
+
+=item C<ret> expected exit status
+
+=item C<timeout> for interactions
+
+=item C<init> initial output before interacting
+
+=item C<inout> [ [ input, list of output checks ], ... ]
+
+=item C<name> of the test
+
+=back
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($cmd, $expected_ret, $timeout, $init, $inout, $name) = @_;
+
+ my $ps = Expect->spawn(@$cmd);
+ ok(defined $ps, "$name command spawn: @$cmd");
+ return unless defined $ps;
+
+ #$ps->slave->stty(qw(raw -echo));
+ $ps->slave->stty(qw(raw));
+ my $n = 0;
+ for my $test (@$inout)
+ {
+ #warn "test: @$test";
+ my ($in, @out) = @$test;
+ $n++;
+ #warn "in: $in";
+ #warn "out: @out";
+ $ps->send($in);
+ for my $o (@out)
+ {
+ my $ok = $ps->expect($timeout, '-re', $o);
+ # check header on first output
+ if (defined $init)
+ {
+ # beware of race conditions...
+ ok($ps->before =~ $init,
+ "$name header matches /$init/");
+ undef $init;
+ }
+ ok($ok, "$n: $name output matches /$o/");
+ if (not $ok)
+ {
+ print STDERR "last input: ###$in####\n";
+ print STDERR "before: ###" . $ps->before . "###\n" if $ps->before;
+ print STDERR "match: ###" . $ps->match . "###\n" if $ps->match;
+ print STDERR "after: ###" . $ps->after . "###\n" if $ps->after;
+ print STDERR "error: ###" . $ps->error . "###\n" if $ps->error;
+ }
+ }
+ }
+ $ps->soft_close();
+
+ # check status
+ my $ret = $ps->exitstatus;
+ ok($ret == $expected_ret,
+ "$name status (got $ret vs expected $expected_ret)");
+
+ return;
+}
+
+=pod
+
=back
=cut
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index ef534a36a0..bbe4a69370 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4348,22 +4348,60 @@ bar 'bar' "bar"
:try_to_quit
\echo `nosuchcommand` :foo :'foo' :"foo"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
- \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+ \a
+ \C arg1
+ \c arg1 arg2 arg3 arg4
+ \cd arg1
+ \conninfo
\copy arg1 arg2 arg3 arg4 arg5 arg6
- \copyright \dt arg1 \e arg1 arg2
+ \copyright
+ SELECT 1 as one, 2, 3 \crosstabview
+ \dt arg1
+ \e arg1 arg2
\ef whole_line
\ev whole_line
- \echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
- \g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
- \o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
- \reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+ \echo arg1 arg2 arg3 arg4 arg5
+ \echo arg1
+ \encoding arg1
+ \errverbose
+ \f arg1
+ \g arg1
+ \gx arg1
+ \gexec
+ SELECT 1 AS one \gset
+ \h
+ \?
+ \html
+ \i arg1
+ \ir arg1
+ \l arg1
+ \lo arg1 arg2
+invalid command \lo
+ \lo_list
+ \o arg1
+ \p
+ \password arg1
+ \prompt arg1 arg2
+ \pset arg1 arg2
+ \q
+ \reset
+ \s arg1
+ \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
+ \setenv arg1 arg2
\sf whole_line
\sv whole_line
- \t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+ \t arg1
+ \T arg1
+ \timing arg1
+ \unset arg1
+ \w arg1
+ \watch arg1
+ \x arg1
-- \else here is eaten as part of OT_FILEPIPE argument
\w |/no/such/file \else
-- \endif here is eaten as part of whole-line argument
\! whole_line \endif
+ \z
\else
\echo 'should print #8-1'
should print #8-1
@@ -4382,6 +4420,17 @@ should print #8-1
\echo '#10-2 ok, variable no_such_variable is not defined'
#10-2 ok, variable no_such_variable is not defined
\endif
+-- elif after true branch
+\if true
+ \echo 'should print #11-1'
+should print #11-1
+\elif true
+ \echo 'should not print #11-2'
+\elif false
+ \echo 'should not print #11-3'
+\else
+ \echo 'should not print #11-4'
+\endif
SELECT :{?i} AS i_is_defined;
i_is_defined
--------------
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 2e37984962..1160855e01 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -911,22 +911,59 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
:try_to_quit
\echo `nosuchcommand` :foo :'foo' :"foo"
\pset fieldsep | `nosuchcommand` :foo :'foo' :"foo"
- \a \C arg1 \c arg1 arg2 arg3 arg4 \cd arg1 \conninfo
+ \a
+ \C arg1
+ \c arg1 arg2 arg3 arg4
+ \cd arg1
+ \conninfo
\copy arg1 arg2 arg3 arg4 arg5 arg6
- \copyright \dt arg1 \e arg1 arg2
+ \copyright
+ SELECT 1 as one, 2, 3 \crosstabview
+ \dt arg1
+ \e arg1 arg2
\ef whole_line
\ev whole_line
- \echo arg1 arg2 arg3 arg4 arg5 \echo arg1 \encoding arg1 \errverbose
- \g arg1 \gx arg1 \gexec \h \html \i arg1 \ir arg1 \l arg1 \lo arg1 arg2
- \o arg1 \p \password arg1 \prompt arg1 arg2 \pset arg1 arg2 \q
- \reset \s arg1 \set arg1 arg2 arg3 arg4 arg5 arg6 arg7 \setenv arg1 arg2
+ \echo arg1 arg2 arg3 arg4 arg5
+ \echo arg1
+ \encoding arg1
+ \errverbose
+ \f arg1
+ \g arg1
+ \gx arg1
+ \gexec
+ SELECT 1 AS one \gset
+ \h
+ \?
+ \html
+ \i arg1
+ \ir arg1
+ \l arg1
+ \lo arg1 arg2
+ \lo_list
+ \o arg1
+ \p
+ \password arg1
+ \prompt arg1 arg2
+ \pset arg1 arg2
+ \q
+ \reset
+ \s arg1
+ \set arg1 arg2 arg3 arg4 arg5 arg6 arg7
+ \setenv arg1 arg2
\sf whole_line
\sv whole_line
- \t arg1 \T arg1 \timing arg1 \unset arg1 \w arg1 \watch arg1 \x arg1
+ \t arg1
+ \T arg1
+ \timing arg1
+ \unset arg1
+ \w arg1
+ \watch arg1
+ \x arg1
-- \else here is eaten as part of OT_FILEPIPE argument
\w |/no/such/file \else
-- \endif here is eaten as part of whole-line argument
\! whole_line \endif
+ \z
\else
\echo 'should print #8-1'
\endif
@@ -945,6 +982,17 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
\echo '#10-2 ok, variable no_such_variable is not defined'
\endif
+-- elif after true branch
+\if true
+ \echo 'should print #11-1'
+\elif true
+ \echo 'should not print #11-2'
+\elif false
+ \echo 'should not print #11-3'
+\else
+ \echo 'should not print #11-4'
+\endif
+
SELECT :{?i} AS i_is_defined;
SELECT NOT :{?no_such_var} AS no_such_var_is_not_defined;
On Wed, Sep 11, 2019 at 10:52:01PM +0200, Fabien COELHO wrote:
AFAICR this is because the coverage was not the same:-) Some backslash
commands just skip silently to the end of the line, so that intermediate
\commands on the same line are not recognized/processed the same, so I moved
everything on one line to avoid this.
I see. So basically this tests for more code paths to ignore
backslash commands and it improves the coverage of \elif. Applied
after fixing two nits:
- Indentation was incorrect.
- Moved the \elif test closer to the existing one for the false
branch (you can grep #2 to find it).
--
Michael
On Wed, Sep 11, 2019 at 10:52:01PM +0200, Fabien COELHO wrote:
AFAICR this is because the coverage was not the same:-) Some backslash
commands just skip silently to the end of the line, so that intermediate
\commands on the same line are not recognized/processed the same, so I moved
everything on one line to avoid this.I see. So basically this tests for more code paths to ignore
backslash commands and it improves the coverage of \elif. Applied
after fixing two nits:
- Indentation was incorrect.
- Moved the \elif test closer to the existing one for the false
branch (you can grep #2 to find it).
Ok. Rebased version added, with some minor changes to improve readability
(comments, variables).
--
Fabien.
Attachments:
psql-tap-4.patchtext/x-diff; name=psql-tap-4.patchDownload
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index b7d36b65dd..aabc27ab6f 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -506,6 +506,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*checksum verification failed/s],
'pg_basebackup reports checksum mismatch');
@@ -526,6 +527,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt2" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*further.*failures.*will.not.be.reported/s],
'pg_basebackup does not report more than 5 checksum mismatches');
@@ -542,6 +544,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt3" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*7 total checksum verification failures/s],
'pg_basebackup correctly report the total number of checksum mismatches');
diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl
index 59228b916c..cf4811d382 100644
--- a/src/bin/pg_checksums/t/002_actions.pl
+++ b/src/bin/pg_checksums/t/002_actions.pl
@@ -62,6 +62,7 @@ sub check_relation_corruption
'--filenode', $relfilenode_corrupted
],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data for single relfilenode on tablespace $tablespace"
@@ -71,6 +72,7 @@ sub check_relation_corruption
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data on tablespace $tablespace");
@@ -203,6 +205,7 @@ sub fail_corrupt
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/^$/],
[qr/could not read block 0 in file.*$file\":/],
"fails for corrupted data in $file");
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
index 3b63ad230f..ebe9b80a52 100644
--- a/src/bin/pg_controldata/t/001_pg_controldata.pl
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -33,6 +33,7 @@ close $fh;
command_checks_all(
[ 'pg_controldata', $node->data_dir ],
0,
+ '',
[
qr/WARNING: Calculated CRC checksum does not match value stored in file/,
qr/WARNING: invalid WAL segment size/
diff --git a/src/bin/pg_resetwal/t/002_corrupted.pl b/src/bin/pg_resetwal/t/002_corrupted.pl
index f9940d7fc5..1990669d26 100644
--- a/src/bin/pg_resetwal/t/002_corrupted.pl
+++ b/src/bin/pg_resetwal/t/002_corrupted.pl
@@ -30,6 +30,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/
@@ -46,6 +47,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index b82d3f65c4..01010914fe 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -50,7 +50,7 @@ sub pgbench
push @cmd, @args;
- $node->command_checks_all(\@cmd, $stat, $out, $err, $name);
+ $node->command_checks_all(\@cmd, $stat, '', $out, $err, $name);
# cleanup?
#unlink @filenames or die "cannot unlink files (@filenames): $!";
diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl
index f7fa18418b..b58f3548c3 100644
--- a/src/bin/pgbench/t/002_pgbench_no_server.pl
+++ b/src/bin/pgbench/t/002_pgbench_no_server.pl
@@ -25,7 +25,7 @@ sub pgbench
my ($opts, $stat, $out, $err, $name) = @_;
print STDERR "opts=$opts, stat=$stat, out=$out, err=$err, name=$name";
command_checks_all([ 'pgbench', split(/\s+/, $opts) ],
- $stat, $out, $err, $name);
+ $stat, '', $out, $err, $name);
return;
}
@@ -52,7 +52,7 @@ sub pgbench_scripts
push @cmd, '-f', $filename;
}
}
- command_checks_all(\@cmd, $stat, $out, $err, $name);
+ command_checks_all(\@cmd, $stat, '', $out, $err, $name);
return;
}
diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore
index c2862b12d6..d324c1c1fa 100644
--- a/src/bin/psql/.gitignore
+++ b/src/bin/psql/.gitignore
@@ -3,3 +3,4 @@
/sql_help.c
/psql
+/tmp_check
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 69bb297fe7..9473ab01cb 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -60,8 +60,15 @@ uninstall:
clean distclean:
rm -f psql$(X) $(OBJS) lex.backup
+ rm -rf tmp_check
# files removed here are supposed to be in the distribution tarball,
# so do not clean them in the clean/distclean rules
maintainer-clean: distclean
rm -f sql_help.h sql_help.c psqlscanslash.c
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/psql/t/001_psql.pl b/src/bin/psql/t/001_psql.pl
new file mode 100644
index 0000000000..f0cf0b4aeb
--- /dev/null
+++ b/src/bin/psql/t/001_psql.pl
@@ -0,0 +1,876 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+my $node = get_new_node('main');
+$node->init();
+$node->start();
+
+# create a file under the test directory
+# return its full path
+sub create_test_file
+{
+ my ($fname, $contents) = @_;
+ my $fn = $node->basedir . '/' . $fname;
+ #ok(not -e $fn, "$fn must not already exists");
+ append_to_file($fn, $contents);
+ return $fn;
+}
+
+# invoke psql
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected exit status
+# - in: input stream
+# - out: list of re to check on stdout
+# - err: list of re to check on stderr
+# - name: of the test
+# - more: more raw arguments
+sub psql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $in, $out, $err, $name, @more) = @_;
+ $opts =~ s/^!// or push @more, '-X';
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+ $node->command_checks_all(\@cmd, $stat, $in, $out, $err, $name);
+ return;
+}
+
+# invoke psql interactively, making it believe it is connected to a tty
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected final exit status
+# - timeout: for interactions, in second
+# - inout: list of input expected re
+# - name: of the test
+# - remove: tell to remove init/start/end checks
+sub ipsql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $timeout, $inout, $name, $remove) = @_;
+
+ # build command to run
+ my @more = ();
+ @more = ('-X') unless $opts =~ /^!/;
+ $opts =~ s/^!//;
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+
+ # check it is running at start and end, and exit cleanly
+ unless ($remove)
+ {
+ unshift @$inout, [ "\\echo STARTING :VERSION_NUM\n", qr/STARTING \d+.*postgres=\# /s ];
+ push @$inout, [ "\\echo ENDING :VERSION_NUM\\q\n", qr/ENDING \d+$/s ];
+ }
+
+ # call the stuff
+ $node->icommand_checks(
+ \@cmd, $stat, $timeout,
+ $remove ? undef : qr/psql .*Type "help" for help..*postgres=# /s,
+ $inout, $name);
+ return;
+}
+
+#
+# check whether we have readline/history capability
+#
+my ($out, $err);
+$node->psql('postgres', "\\s\n", 'stdout' => \$out, 'stderr' => \$err);
+my $has_history = $err !~ /history is not supported by this installation/;
+
+my $EMPTY = [ qr/^$/ ];
+
+#
+# check various options
+#
+# this cannot be tested from within a SQL script
+psql('!--help', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql --help');
+psql('!-?', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql -?');
+psql('!--version', 0, '',
+ [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql --version');
+psql('--help=variables', 0, '',
+ [ qr{VERSION_NAME}, qr{unicode_border_linestyle}, qr{PSQL_EDITOR} ],
+ $EMPTY, 'psql --help=variables');
+# look for plenty of backslash-commands
+psql('--help=commands', 0, '',
+ [ qr{\\copyright}, qr{\\pset}, qr{(?!VERSION_NAME)} ],
+ $EMPTY, 'psql --help=commands');
+
+# tested elsewhere: -a -A -d -e -h -L
+psql('-b', 0, "SELECT 1/0;\n",
+ $EMPTY, [ qr{STATEMENT: SELECT 1/0}, qr{ERROR: division by zero} ], 'psql -b');
+psql('-c \\q', 0, '', $EMPTY, $EMPTY, 'psql -c');
+psql('-c \\l -c \\l', 0, '', [ qr{template0.*template0}s ], $EMPTY, 'psql -c -c');
+psql('', 0, '', [ qr{(?!1234)}, qr{5678}s ], $EMPTY, 'psql -c ;;', ('-c', 'SELECT 1234; SELECT 5678;'));
+psql('-E', 0, "\\l\n", [ qr{\* QUERY \*}, qr{template0} ], $EMPTY, 'psql -E');
+psql('-A -F _', 0, "SELECT 54, 32;\n", [ qr{54_32} ], $EMPTY, 'psql -F _');
+psql('-H', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -H');
+psql('-l', 0, '', [ qr{template0}, qr{template1} ], $EMPTY, 'psql -l');
+psql('-n', 0, "\\q\n", $EMPTY, $EMPTY, 'psql -n');
+psql('-P format=html', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -P format=html');
+psql('-P tuples_only', 0, "\\pset\n", [ qr{tuples_only.*on} ], $EMPTY, 'psql -P tuples_only');
+psql('-A -R_', 0, "VALUES (1), (2);\n", [ qr{_1_2_} ], $EMPTY, 'psql -R _');
+psql('-s', 0, "SELECT 1;\n", [ qr{verify command}, qr{SELECT 1}, qr{press return to proceed} ], $EMPTY, 'psql -s');
+psql('-S', 0, "SELECT 1 AS one\nSELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], $EMPTY, 'psql -S');
+psql('-H -T width=100', 0, "SELECT 1 AS one;\n", [ qr{table.*width=100} ], $EMPTY, 'psql -T ...');
+psql('-X -V', 0, '', [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql -X -V');
+psql('', 0, "\\echo :VERSION_NUM\n", [ qr{\d{6}} ], $EMPTY, 'psql -- show VERSION_NUM');
+psql('-v VERSION_NUM', 0, "\\echo :VERSION_NUM\n", [ qr{:VERSION_NUM} ], $EMPTY, 'psql -v ...');
+psql('-v VERSION_NUM=bla', 0, "\\echo :VERSION_NUM\n", [ qr{bla} ], $EMPTY, 'psql -v ...=.');
+psql('-w', 0, '', $EMPTY, $EMPTY, 'psql -w');
+# nope, interacts on tty
+#psql('-W', 0, "foo\n", [ qr{^$} ], [ qr{^$} ], 'psql -W');
+psql('-x', 0, "SELECT 1 AS one, 2 AS two;\n", [ qr{one \| 1.*two \| 2}s ], $EMPTY, 'psql -x');
+# some issue, \0 is not welcome somewhere
+#psql('-A -z', "SELECT 1 AS one, 2 AS two;\n", [ qr{one.two}s, qr{1.2}s ], $EMPTY, 'psql -z');
+#psql('-A -0', "SELECT 1 AS one, 2 AS two;\n", [ qr{two.1}s ], $EMPTY, 'psql -0');
+psql('-1', 0, "SELECT 54;\nSELECT 32;\n", [ qr{54}, qr{32} ], $EMPTY, 'psql -1');
+psql('-X -?', 0, '',
+ [ qr{interactive terminal}, qr{Usage:}, qr{Connection options:} ], $EMPTY, 'psql -X -?');
+psql('--csv', 0, "SELECT 1 AS one, 2 AS two\n", [ qr{one,two}, qr{1,2} ], $EMPTY, 'psql --csv');
+
+#
+# create some objects to help \d tests later
+#
+psql('', 0, '-- create some objects
+CREATE USER regress_psql_tap_1;
+ALTER ROLE regress_psql_tap_1 SET enable_indexscan TO off;
+CREATE TABLE regress_psql_tap_1_t1(data TEXT NOT NULL);
+CREATE FUNCTION regress_psql_tap_1_f1() RETURNS INTEGER AS $$ SELECT 54321 $$ LANGUAGE SQL;
+CREATE VIEW regress_psql_tap_1_v1 AS SELECT 54321 AS one;
+', [ qr{CREATE ROLE}, qr{ALTER ROLE} ], [ qr{^$} ], 'psql -- create stuff');
+
+#
+# check interactive help
+#
+# this is mostly for coverage, including occasional negatives.
+# we certainly do not want to have an output file with the whole doc.
+my @backslash_help = (
+ # \h command, list of stdout matches
+ [ 'no-help', [ qr{No help available for} ] ],
+ [ '' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ [ ' ' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ # lists
+ [ 'ALTER', [ qr{AGGREGATE}, qr{ROLE}, qr{VIEW}, qr{(?!ACCESS METHOD)}, qr{(?!TRANSFORM)} ] ],
+ [ 'ALTER TEXT SEARCH', [ qr{CONFIGURATION}, qr{DICTIONARY}, qr{PARSER}, qr{TEMPLATE} ] ],
+ [ 'ALTER FOREIGN', [ qr{DATA WRAPPER}, qr{TABLE} ] ],
+ [ 'CREATE', [ qr{ACCESS METHOD}, qr{TRANSFORM} ] ],
+ [ 'DROP', [ qr{ACCESS METHOD}, qr{VIEW} ] ],
+ # details
+ [ 'ABORT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'ALTER AGGREGATE', [ qr{RENAME TO}, qr{(?!CONVERSION)} ] ],
+ [ 'ALTER CONVERSION', [ qr{SET SCHEMA}, qr{(?!AGGREGATE)} ] ],
+ [ 'ALTER COLLATION', [ qr{REFRESH VERSION} ] ],
+ [ 'ALTER DATABASE', [ qr{CONNECTION LIMIT} ] ],
+ [ 'ALTER DEFAULT PRIVILEGES', [ qr{GRANT} ] ],
+ [ 'ALTER DOMAIN', [ qr{VALIDATE CONSTRAINT} ] ],
+ [ 'ALTER EVENT TRIGGER', [ qr{DISABLE} ] ],
+ [ 'ALTER EXTENSION', [ qr{OPERATOR FAMILY} ] ],
+ [ 'ALTER FOREIGN DATA WRAPPER', [ qr{VALIDATOR} ] ],
+ [ 'ALTER FOREIGN TABLE', [ qr{SET STORAGE} ] ],
+ [ 'ALTER FUNCTION', [ qr{PARALLEL} ] ],
+ [ 'ALTER GROUP', [ qr{DROP USER} ] ],
+ [ 'ALTER INDEX', [ qr{ALL IN TABLESPACE} ] ],
+ [ 'ALTER LANGUAGE', [ qr{PROCEDURAL}, qr{(?!INDEX)} ] ],
+ [ 'ALTER LARGE OBJECT', [ qr{OWNER TO} ] ],
+ [ 'ALTER MATERIALIZED VIEW', [ qr{SET TABLESPACE} ] ],
+ [ 'ALTER OPERATOR', [ qr{JOIN =} ] ],
+ [ 'ALTER OPERATOR CLASS', [ qr{SET SCHEMA}, qr{(?!LANGUAGE)} ] ],
+ [ 'ALTER OPERATOR FAMILY', [ qr{FUNCTION} ] ],
+ [ 'ALTER POLICY', [ qr{WITH CHECK} ] ],
+ [ 'ALTER PROCEDURE', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER PUBLICATION', [ qr{DROP TABLE} ] ],
+ [ 'ALTER ROLE', [ qr{BYPASSRLS} ] ],
+ [ 'ALTER ROUTINE', [ qr{LEAKPROOF} ] ],
+ [ 'ALTER RULE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER SCHEMA', [ qr{OWNER TO} ] ],
+ [ 'ALTER SEQUENCE', [ qr{NO MAXVALUE} ] ],
+ [ 'ALTER SERVER', [ qr{VERSION} ] ],
+ [ 'ALTER STATISTICS', [ qr{SET SCHEMA} ] ],
+ [ 'ALTER SUBSCRIPTION', [ qr{REFRESH PUBLICATION} ] ],
+ [ 'ALTER SYSTEM', [ qr{RESET ALL} ] ],
+ [ 'ALTER TABLE', [ qr{ALL IN TABLESPACE}, qr{DISABLE ROW LEVEL SECURITY} ] ],
+ [ 'ALTER TABLESPACE', [ qr{RESET} ] ],
+ [ 'ALTER TEXT SEARCH CONFIGURATION', [ qr{ALTER MAPPING FOR} ] ],
+ [ 'ALTER TEXT SEARCH DICTIONARY', [ qr{OWNER TO} ] ],
+ [ 'ALTER TEXT SEARCH PARSER', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TEXT SEARCH TEMPLATE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TRIGGER', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER TYPE', [ qr{RENAME ATTRIBUTE} ] ],
+ [ 'ALTER USER', [ qr{NOREPLICATION}, qr{(?!MAPPING)} ] ],
+ [ 'ALTER USER MAPPING', [ qr{SERVER} ] ],
+ [ 'ALTER VIEW', [ qr{DROP DEFAULT} ] ],
+ [ 'ANALYZE', [ qr{VERBOSE}, qr{SKIP_LOCKED} ] ],
+ [ 'BEGIN', [ qr{ISOLATION LEVEL} ] ],
+ [ 'CALL', [] ],
+ [ 'CHECKPOINT', [] ],
+ [ 'CLOSE', [ qr{ALL} ] ],
+ [ 'CLUSTER', [ qr{VERBOSE} ] ],
+ [ 'COMMENT', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'COMMIT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'COMMIT PREPARED', [] ],
+ [ 'COPY', [ qr{FORMAT}, qr{FORCE_NOT_NULL} ] ],
+ [ 'CREATE ACCESS METHOD', [ qr{HANDLER} ] ],
+ [ 'CREATE AGGREGATE', [ qr{SSPACE}, qr{SORTOP}, qr{FINALFUNC} ] ],
+ [ 'CREATE CAST', [ qr{AS IMPLICIT} ] ],
+ [ 'CREATE COLLATION', [ qr{LC_CTYPE}, qr{DETERMINISTIC} ] ],
+ [ 'CREATE CONVERSION', [ qr{FOR} ] ],
+ [ 'CREATE DATABASE', [ qr{ALLOW_CONNECTIONS} ] ],
+ [ 'CREATE DOMAIN', [ qr{COLLATE} ] ],
+ [ 'CREATE EVENT TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE EXTENSION', [ qr{FROM} ] ],
+ [ 'CREATE FOREIGN DATA WRAPPER', [ qr{NO VALIDATOR} ] ],
+ [ 'CREATE FOREIGN TABLE', [ qr{SERVER} ] ],
+ [ 'CREATE FUNCTION', [ qr{CALLED ON NULL INPUT}, qr{(?!PROCEDURE)} ] ],
+ [ 'CREATE GROUP', [ qr{ADMIN} ] ],
+ [ 'CREATE INDEX', [ qr{CONCURRENTLY}, qr{WHERE} ] ],
+ [ 'CREATE LANGUAGE', [ qr{TRUSTED} ] ],
+ [ 'CREATE MATERIALIZED VIEW', [ qr{DATA} ] ],
+ [ 'CREATE OPERATOR', [ qr{LEFTARG} ] ],
+ [ 'CREATE OPERATOR CLASS', [ qr{FOR ORDER BY} ] ],
+ [ 'CREATE OPERATOR FAMILY', [ qr{USING} ] ],
+ [ 'CREATE POLICY', [ qr{PERMISSIVE} ] ],
+ [ 'CREATE PROCEDURE', [ qr{SECURITY INVOKER}, qr{(?!FUNCTION)} ] ],
+ [ 'CREATE PUBLICATION', [ qr{FOR ALL TABLES} ] ],
+ [ 'CREATE ROLE', [ qr{SYSID} ] ],
+ [ 'CREATE RULE', [ qr{ALSO} ] ],
+ [ 'CREATE SCHEMA', [ qr{AUTHORIZATION} ] ],
+ [ 'CREATE SEQUENCE', [ qr{NO MINVALUE} ] ],
+ [ 'CREATE SERVER', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'CREATE STATISTICS', [ qr{FROM} ] ],
+ [ 'CREATE SUBSCRIPTION', [ qr{CONNECTION} ] ],
+ [ 'CREATE TABLE', [ qr{PARTITION BY} ] ],
+ [ 'CREATE TABLE AS', [ qr{ON COMMIT} ] ],
+ [ 'CREATE TABLESPACE', [ qr{LOCATION} ] ],
+ [ 'CREATE TEXT SEARCH CONFIGURATION', [ qr{PARSER} ] ],
+ [ 'CREATE TEXT SEARCH DICTIONARY', [ qr{TEMPLATE} ] ],
+ [ 'CREATE TEXT SEARCH PARSER', [ qr{GETTOKEN} ] ],
+ [ 'CREATE TEXT SEARCH TEMPLATE', [ qr{LEXIZE} ] ],
+ [ 'CREATE TRANSFORM', [ qr{FROM SQL WITH FUNCTION} ] ],
+ [ 'CREATE TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE TYPE', [ qr{SUBTYPE} ] ],
+ [ 'CREATE USER', [ qr{PASSWORD NULL} ] ],
+ [ 'CREATE USER MAPPING', [ qr{SERVER} ] ],
+ [ 'CREATE VIEW', [ qr{CASCADED} ] ],
+ [ 'DEALLOCATE', [ qr{PREPARE} ] ],
+ [ 'DECLARE', [ qr{INSENSITIVE} ] ],
+ [ 'DELETE', [ qr{WHERE CURRENT OF} ] ],
+ [ 'DISCARD', [ qr{PLANS} ] ],
+ [ 'DO', [ qr{LANGUAGE} ] ],
+ [ 'DROP ACCESS METHOD', [ qr{CASCADE} ] ],
+ [ 'DROP AGGREGATE', [ qr{ORDER BY} ] ],
+ [ 'DROP CAST', [ qr{RESTRICT} ] ],
+ [ 'DROP COLLATION', [ qr{RESTRICT} ] ],
+ [ 'DROP CONVERSION', [ qr{RESTRICT} ] ],
+ [ 'DROP DATABASE', [ qr{IF EXISTS} ] ],
+ [ 'DROP DOMAIN', [ qr{RESTRICT} ] ],
+ [ 'DROP EVENT TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP EXTENSION', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN DATA WRAPPER', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP FUNCTION', [ qr{RESTRICT} ] ],
+ [ 'DROP GROUP', [ qr{IF EXISTS} ] ],
+ [ 'DROP INDEX', [ qr{RESTRICT} ] ],
+ [ 'DROP LANGUAGE', [ qr{RESTRICT} ] ],
+ [ 'DROP MATERIALIZED VIEW', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR CLASS', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR FAMILY', [ qr{RESTRICT} ] ],
+ [ 'DROP OWNED', [ qr{CURRENT_USER} ] ],
+ [ 'DROP POLICY', [ qr{RESTRICT} ] ],
+ [ 'DROP PROCEDURE', [ qr{RESTRICT} ] ],
+ [ 'DROP PUBLICATION', [ qr{RESTRICT} ] ],
+ [ 'DROP ROLE', [ qr{IF EXISTS} ] ],
+ [ 'DROP ROUTINE', [ qr{RESTRICT} ] ],
+ [ 'DROP RULE', [ qr{RESTRICT} ] ],
+ [ 'DROP SCHEMA', [ qr{RESTRICT} ] ],
+ [ 'DROP SEQUENCE', [ qr{RESTRICT} ] ],
+ [ 'DROP SERVER', [ qr{RESTRICT} ] ],
+ [ 'DROP STATISTICS', [ qr{IF EXISTS} ] ],
+ [ 'DROP SUBSCRIPTION', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLESPACE', [ qr{IF EXISTS} ] ],
+ [ 'DROP TEXT SEARCH CONFIGURATION', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH DICTIONARY', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH PARSER', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH TEMPLATE', [ qr{RESTRICT} ] ],
+ [ 'DROP TRANSFORM', [ qr{LANGUAGE} ] ],
+ [ 'DROP TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP TYPE', [ qr{RESTRICT} ] ],
+ [ 'DROP USER', [ qr{IF EXISTS} ] ],
+ [ 'DROP USER MAPPING', [ qr{SERVER} ] ],
+ [ 'DROP VIEW', [ qr{RESTRICT} ] ],
+ [ 'END', [ qr{TRANSACTION}, qr{CHAIN}, qr{(?!COMMIT)} ] ],
+ [ 'EXECUTE', [ ] ],
+ [ 'EXPLAIN', [ qr{VERBOSE} ] ],
+ [ 'FETCH', [ qr{RELATIVE} ] ],
+ [ 'GRANT', [ qr{ALL TABLES} ] ],
+ [ 'IMPORT FOREIGN SCHEMA', [ qr{FROM SERVER} ] ],
+ [ 'INSERT', [ qr{ON CONFLICT} ] ],
+ [ 'LISTEN', [ ] ],
+ [ 'LOAD', [ ] ],
+ [ 'LOCK', [ qr{NOWAIT} ] ],
+ [ 'MOVE', [ qr{FORWARD} ] ],
+ [ 'NOTIFY', [ ] ],
+ [ 'PREPARE', [ qr{AS} ] ],
+ [ 'PREPARE TRANSACTION', [ ] ],
+ [ 'REASSIGN OWNED', [ qr{SESSION_USER} ] ],
+ [ 'REFRESH MATERIALIZED VIEW', [ qr{CONCURRENTLY} ] ],
+ [ 'REINDEX', [ qr{DATABASE} ] ],
+ [ 'RELEASE', [ qr{SAVEPOINT} ] ],
+ [ 'RESET', [ qr{RESET ALL} ] ],
+ [ 'REVOKE', [ qr{PRIVILEGES} ] ],
+ [ 'ROLLBACK', [ qr{CHAIN} ] ],
+ [ 'ROLLBACK PREPARED', [ ]],
+ [ 'SAVEPOINT', [ ] ],
+ [ 'SECURITY LABEL', [ qr{SUBSCRIPTION} ] ],
+ [ 'SELECT', [ qr{RECURSIVE} ] ],
+ [ 'SET', [ qr{SESSION} ] ],
+ [ 'SET CONSTRAINTS', [ qr{IMMEDIATE} ] ],
+ [ 'SET ROLE', [ qr{ROLE NONE} ] ],
+ [ 'SET SESSION AUTHORIZATION', [ qr{DEFAULT} ] ],
+ [ 'SET TRANSACTION', [ qr{SNAPSHOT} ] ],
+ [ 'SHOW', [ qr{SHOW ALL} ] ],
+ [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ],
+ [ 'TABLE', [ qr{ONLY} ] ], # hmmm...
+ [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ],
+ [ 'UNLISTEN', [ ] ],
+ [ 'UPDATE', [ qr{RETURNING} ] ],
+ [ 'VACUUM', [ qr{FREEZE} ] ],
+ [ 'VALUES', [ qr{ORDER BY} ] ],
+ [ 'WITH', [ qr{RECURSIVE} ] ], # SELECT duplicate?
+);
+
+for my $h (@backslash_help)
+{
+ my ($cmd, $out) = @$h;
+ push @$out, qr{$cmd};
+ psql('', 0, "\\h $cmd\n", $out, [ qr{^$} ], "psql -- \\h $cmd");
+}
+
+# special cases
+psql('', 0, "\\h ROLLBACK TO SAVEPOINT\n", [ qr{ROLLBACK}, qr{SAVEPOINT} ],
+ [ qr{^$} ], "psql -- \\h ROLLBACK TO SAVEPOINT");
+psql('', 0, "\\h SELECT INTO\n", [ qr{SELECT}, qr{INTO} ],
+ [ qr{^$} ], "psql -- \\h SELECT INTO");
+
+#
+# check describe and other backslash commands
+#
+# the output can vary significantly, especially with +
+my @backslash_out = (
+ # empty
+ [ "\\d\n", [ qr{List of relations}, qr{regress_psql_tap_1_t1}, qr{(?!pg_locks)} ] ],
+ [ "\\dS\n", [ qr{List of relations}, qr{pg_locks} ] ],
+ # aggregates
+ [ "\\da\n", [ qr{List of aggregate functions}, qr{(?!Description)} ] ],
+ [ "\\da+\n", [ qr{List of aggregate functions}, qr{(Description)} ] ],
+ [ "\\daS\n", [ qr{array_agg}, qr{avg}, qr{bit_and}, qr{bit_or}, qr{count}, qr{max} ] ],
+ # access methods
+ [ "\\dA\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{(?!Description)} ] ],
+ [ "\\dA+\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{Description} ] ],
+ # tablespaces
+ [ "\\db\n", [ qr{List of tablespaces}, qr{pg_default}, qr{pg_global}, qr{(?!Size)} ] ],
+ [ "\\db+ pg_def*\n", [ qr{List of tablespaces}, qr{pg_default}, qr{(?!pg_global)}, qr{Size} ] ],
+ # functions
+ [ "\\df\n", [ qr{List of functions} ] ],
+ [ "\\df+\n", [ qr{List of functions} ] ],
+ [ "\\dftS\n", [ qr{RI_}, qr{Type}, qr{(?!Volatility)}, qr{(?!Parallel)} ] ],
+ [ "\\dftS+\n", [ qr{Volatility}, qr{Parallel} ] ],
+ [ "\\dfwS\n", [ qr{lag}, qr{dense_rank}, qr{(?!Owner)}, qr{(?!Language)} ] ],
+ [ "\\dfwS+\n", [ qr{cume_dist}, qr{Owner}, qr{Language} ] ],
+ [ "\\dfnS\n", [ qr{abbrev}, qr{(?!bit_or)} ] ],
+ [ "\\dfaS\n", [ qr{bit_and}, qr{(?!abs)} ] ],
+ [ "\\dfpS\n", [ qr{List of functions} ] ],
+ [ "\\dfwtS\n", [ qr{RI_FKey_}, qr{last_value}, qr{nth_value}, qr{(?!bit_and)} ] ],
+ # type
+ [ "\\dTS charac*\n", [ qr{List of data types}, qr{character varying}, qr{(?!double)}, qr{(?!Size)} ] ],
+ [ "\\dTS+\n", [ qr{aclitem}, qr{Size} ] ],
+ # operator
+ [ "\\doS\n", [ qr{List of operators}, qr{!~\*} ] ],
+ [ "\\doS+\n", [ qr{<->}, qr{Result type}] ],
+ # databases
+ [ "\\l\n", [ qr{List of databases}, qr{template0}, qr{template1}, qr{(?!Size)} ] ],
+ [ "\\l a*\n", [ qr{(?!template[01])} ] ],
+ [ "\\l+\n", [ qr{Size}, qr{Encoding} ] ],
+ [ "\\list\n", [ qr{List of databases} ] ],
+ # permissions: well tested
+ # default acls (empty)
+ [ "\\ddp\n", [ qr{Default access privileges} ] ],
+ # descriptions (empty)
+ [ "\\dd\n", [ qr{Object descriptions} ] ],
+ # tables: well tested
+ # roles/users/groups
+ [ "\\du\n", [ qr{Superuser}, qr{regress_psql_tap_1}, qr{List of roles}, qr{(?!Description)} ] ],
+ [ "\\du+\n", [ qr{Superuser}, qr{List of roles}, qr{Description} ] ],
+ [ "\\dg\n", [ qr{Superuser}, qr{List of roles} ] ],
+ # role settings
+ [ "\\drds\n", [ qr{List of settings} ] ],
+ [ "\\drds regress_* *\n", [ qr{List of settings} ] ],
+ # index/...
+ [ "\\diS\n", [ qr{List of relations}, qr{pg_am_oid_index} ] ],
+ [ "\\diS+\n", [ qr{Table} ] ],
+ # partition tables: well tested
+ # large objects
+ [ "\\dl", [ qr{Large objects} ] ],
+ # languages
+ [ "\\dL\n", [ qr{List of languages}, qr{plpgsql}, qr{(?!Trusted)} ] ],
+ [ "\\dL+\n", [ qr{plpgsql}, qr{Trusted} ] ],
+ # domains
+ [ "\\dD\n", [ qr{List of domains}, qr{(?!Access privileges)} ] ],
+ [ "\\dD+\n", [ qr{List of domains}, qr{Access privileges} ] ],
+ # conversions
+ [ "\\dcS\n", [ qr{List of conversions}, qr{UTF8}, qr{(?!Description)} ] ],
+ [ "\\dcS+\n", [ qr{List of conversions}, qr{LATIN1}, qr{Description} ] ],
+ # event triggers (empty)
+ [ "\\dy\n", [ qr{List of event triggers}, qr{(?!Description)} ] ],
+ [ "\\dy+\n", [ qr{List of event triggers}, qr{Description} ] ],
+ # casts
+ [ "\\dC\n", [ qr{List of casts}, qr{timestamp without time zone}, qr{(?!Description)} ] ],
+ [ "\\dC+\n", [ qr{Function}, qr{Implicit}, qr{Description} ] ],
+ # collations
+ [ "\\dOS\n", [ qr{List of collations}, qr{Deterministic}, qr{(?!Description)} ] ],
+ [ "\\dOS+\n", [ qr{POSIX}, qr{Description} ] ],
+ # schemas
+ [ "\\dn\n", [ qr{List of schemas}, qr{Owner}, qr{(?!Description)} ] ],
+ [ "\\dn+\n", [ qr{Access privileges}, qr{Description} ] ],
+ # text search misc.
+ [ "\\dFp\n", [ qr{List of text search parsers}, qr{default} ] ],
+ [ "\\dFp+\n", [ qr{hword}, qr{numword} ] ],
+ [ "\\dFd\n", [ qr{List of text search dictionaries}, qr{simple} ] ],
+ [ "\\dFd+\n", [ qr{Init options}, qr{Description} ] ],
+ [ "\\dFt\n", [ qr{List of text search templates}, qr{simple} ] ],
+ [ "\\dFt+\n", [ qr{Lexize}, qr{Description} ] ],
+ [ "\\dF\n", [ qr{List of text search configurations}, qr{simple} ] ],
+ [ "\\dF+\n", [ qr{email}, qr{url_path} ] ],
+ # extensions
+ [ "\\dx\n", [ qr{List of installed extensions}, qr{Version}, qr{plpgsql} ] ],
+ [ "\\dx+\n", [ qr{Objects in extension}, qr{plpgsql_call_handler} ] ],
+ # other backslash commands
+ [ "\\f\n", [ qr{Field separator is} ] ],
+ [ "\\H\\H\n", [ qr{Output format is html.* is aligned}s ] ],
+ [ "\\\?\n", [ qr{General}, qr{Help}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\C foobar\n", [ qr{Title is "foobar"} ] ],
+ [ "\\cd /\n\\cd\n", [ qr{^$} ] ],
+ [ "\\conninfo\n", [ qr{You are connected to database} ] ],
+ [ "\\copy (SELECT 5432 UNION SELECT 2345 ORDER BY 1) TO STDOUT\n", [ qr/\b2345\b.*\b5432\b/s ] ],
+ [ "\\copyright\n", [ qr/The Regents of the University of California/ ] ],
+ [ "\\encoding\n", [ qr/./ ] ],
+ [ "\\encoding :ENCODING\n", $EMPTY ],
+ [ "\\errverbose\n", [ qr{There is no previous error} ] ],
+ [ "\\lo_list\n", [ qr{Large objects} ] ],
+ [ "\\if true\\q\\endif\n", $EMPTY ],
+ # ???
+ #[ "SELECT md5('hello world');\n\\s\n", [ qr{5eb63bbbe0}, qr{SELECT md5} ] ],
+ [ "\\set\n", [ qr{ENCODING = }, qr{VERSION_NUM = } ] ],
+ [ "\\set COMP_KEYWORD_CASE preserve-lower\n\\set COMP_KEYWORD_CASE lower\n" .
+ "\\set COMP_KEYWORD_CASE upper\n\\echo :COMP_KEYWORD_CASE", [ qr/upper/ ] ],
+ [ "\\set HISTCONTROL ignorespace\n\\set HISTCONTROL ignoredups\n" .
+ "\\set HISTCONTROL ignoreboth\n\\echo :HISTCONTROL", [ qr/ignoreboth/ ] ],
+ [ "\\set ECHO_HIDDEN on\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/template0/ ] ],
+ [ "\\set ECHO_HIDDEN noexec\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/(?!template0)/ ] ],
+ [ "\\set LAST_ERROR_MESSAGE foo bla\n\\echo :LAST_ERROR_MESSAGE\n", [ qr{foobla} ] ],
+ [ "\\set AUTOCOMMIT off\n\\set ON_ERROR_ROLLBACK on\nSELECT 5432 AS pg;\nSELECT 2345 AS pg;\n",
+ [ qr{5432}, qr{2345} ] ],
+ [ "\\set ON_ERROR_ROLLBACK interactive\n\\echo :ON_ERROR_ROLLBACK\n", [ qr{interactive} ] ],
+ [ "\\set VERBOSITY verbose\n\\echo :VERBOSITY\n", [ qr{verbose} ] ],
+ [ "\\set foo bla\n\\echo :foo\\unset foo\\echo :foo\n", [ qr{^bla.*:foo}s ] ],
+ [ "\\set foo bla\n\\echo :'foo' :\"foo\"\n", [ qr{'bla'}, qr{"bla"} ] ],
+ [ "\\setenv FOO bla\n\\setenv FOO\n", [ qr{^$} ] ],
+ [ "\\timing\nSELECT 1;\n\\timing\n", [ qr{Timing is on.*Timing is off}s, qr(Time: \d+\.\d{3} ms) ] ],
+ [ "\\timing ON\n\\timing OFF\n", [ qr{Timing is on.*Timing is off}s ] ],
+ [ "\\T foo\n", [ qr{Table attributes are "foo"} ] ],
+ [ "\\T\n", [ qr{Table attributes unset} ] ],
+ # help variants
+ [ "\\?\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? commands\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? bad\n", [ qr{General} ] ],
+ [ "\\? variables\n",
+ [ qr{psql variables:}, qr{Display settings:}, qr{Environment variables} ] ],
+ [ "\\? options\n",
+ [ qr{interactive terminal}, qr{Report bugs} ] ],
+ # pset format
+ [ "\\pset format aligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is aligned}, qr{ 1 \| 2} ] ],
+ [ "\\pset format asciidoc\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is asciidoc}, qr{\|1 \|2} ] ],
+ [ "\\pset format csv\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is csv}, qr{1,2} ] ],
+ [ "\\pset format html\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is html}, qr{<td align="right">1</td>} ] ],
+ [ "\\pset format latex\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex}, qr{1 \& 2} ] ],
+ [ "\\pset format latex-longtable\n\\pset format\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex-longtable}, qr{\\raggedright\{1\}} ] ],
+ [ "\\pset format troff-ms\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is troff-ms}, qr{1\t2} ] ],
+ [ "\\pset format unaligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is unaligned}, qr{1\|2} ] ],
+ [ "\\pset format wrapped\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is wrapped}, qr{ 1 \| 2} ] ],
+ [ "\\pset format\n", [ qr{Output format is aligned} ] ],
+ # pset linestyle
+ [ "\\pset linestyle ascii\n", [ qr{Line style is ascii} ] ],
+ [ "\\pset linestyle old-ascii\n", [ qr{Line style is old-ascii} ] ],
+ [ "\\pset linestyle unicode\n", [ qr{Line style is unicode} ] ],
+ # pset unicode_*_linestyle
+ [ "\\pset unicode_header_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_header_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_border_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_border_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_column_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_column_linestyle double\n", [ qr{"double"} ] ],
+);
+
+for my $h (@backslash_out)
+{
+ my ($in, $out) = @$h;
+ psql('', 0, $in, $out, [ qr{^$} ], "psql -- $in");
+}
+
+# test some errors
+my @backslash_err = (
+ [ "\\unknown stuff\n", [ qr/invalid command / ] ],
+ [ "\\cd /no/such/dir\n", [ qr/could not change directory to/ ] ],
+ [ "\\copy\n", [ qr/arguments required/ ] ],
+ [ "\\copy bad stuff on the line\n", [ qr/parse error at \"/ ] ],
+ [ "\\copy binary (\n", [ qr/parse error at end of line/ ] ],
+ [ "\\copy public.regress_psql_tap_1_t1(data) to '/no/such/file'", [ qr{/no/such/file} ] ],
+ [ "\\encoding no-such-encoding\n", [ qr/invalid encoding name/ ] ],
+ [ "BAD;\n\\errverbose\n", [ qr/syntax error at or near "BAD"/, qr/\b42601\b/ ] ],
+ [ "\\dfX\n", [ qr/invalid command/ ] ],
+ [ "\\dPX\n", [ qr/invalid command/ ] ],
+ [ "\\drX\n", [ qr/invalid command/ ] ],
+ [ "\\dRX\n", [ qr/invalid command/ ] ],
+ [ "\\dFX\n", [ qr/invalid command/ ] ],
+ [ "\\deX\n", [ qr/invalid command/ ] ],
+ [ "\\dX\n", [ qr/invalid command/ ] ],
+ [ "\\i\n", [ qr/missing required argument/ ] ],
+ [ "\\ir\n", [ qr/missing required argument/] ],
+ [ "\\lo_bad\n", [ qr/invalid command/ ] ],
+ [ "\\lo_export\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import /no/such/file\n", [ qr{no/such/file} ] ],
+ [ "\\lo_unlink\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_unlink -1\n", [ qr/large object 4294967295 does not exist/ ] ],
+ [ "\\setenv\n", [ qr/missing required argument/ ] ],
+ [ "\\setenv PG_REGRESS_TEST=1\n", [ qr/environment variable name must not contain "="/ ] ],
+ [ "\\unset\n", [ qr/missing required argument/ ] ],
+ [ "\\set foo \`no-such-command\`\n", [ qr/no-such-command/ ] ],
+ [ "\\set === xxx\n", [ qr/invalid variable name/ ] ],
+ [ "\\w\n", [ qr/missing required argument/ ] ],
+ [ "\\pset format bad\n", [ qr/allowed formats are aligned, asciidoc/ ] ],
+ [ "\\pset linestyle bad\n", [ qr/allowed line styles are ascii, old-ascii, unicode/ ] ],
+ [ "\\pset unicode_border_linestyle bad\n", [ qr/allowed Unicode border line styles are single, double/ ] ],
+ [ "\\pset unicode_column_linestyle bad\n", [ qr/allowed Unicode column line styles are single, double/ ] ],
+ [ "\\pset unicode_header_linestyle bad\n", [ qr/allowed Unicode header line styles are single, double/ ] ],
+ [ "COPY regress_psql_tap_1_t1 FROM STDIN \\watch 0.01\n", [ qr/watch cannot be used with COPY/ ] ],
+ [ "\\watch 0.01\n", [ qr/watch cannot be used with an empty query/ ] ],
+ # check variable constraints values
+ [ "\\set ECHO bad\n", [ qr{none, errors, queries, all} ] ],
+ [ "\\set ECHO_HIDDEN bad\n", [ qr{on, off, noexec} ] ],
+ [ "\\set ON_ERROR_ROLLBACK bad\n", [ qr{on, off, interactive} ] ],
+ [ "\\set COMP_KEYWORD_CASE bad\n", [ qr{lower, upper, preserve-lower, preserve-upper} ] ],
+ [ "\\set HISTCONTROL bad\n", [ qr{none, ignorespace, ignoredups, ignoreboth} ] ],
+ [ "\\set VERBOSITY bad\n", [ qr{default, verbose, terse, sqlstate} ] ],
+ [ "\\set SHOW_CONTEXT bad\n", [ qr{never, errors, always} ] ],
+);
+
+for my $h (@backslash_err)
+{
+ my ($in, $err) = @$h;
+ psql('-L /dev/null', 0, $in, [ qr{^$} ], $err, "psql -- $in");
+}
+
+# other errors
+
+# \timing
+psql('', 0, "\\timing bad\n",
+ [ qr{Timing is off} ], [ qr{unrecognized value} ], 'psql \timing error');
+
+# check stdout vs stderr output
+psql('', 0, "\\echo hello\n\\warn world\n\\q\n",
+ [ qr{^hello$} ], [ qr{^world$} ], 'psql in/out/err');
+
+# \watch test
+psql('', 0,
+ "CREATE TEMP SEQUENCE tmp_seq MAXVALUE 3 NO CYCLE;\n" .
+ "\\t on\n" .
+ "\\timing\n" .
+ "SELECT 'x=' || NEXTVAL('tmp_seq') \\watch 0.01\n",
+ [ qr/x=1\b.*x=2\b.*x=3\b/s, qr/(?!x=[04-9])/ ],
+ [ qr/nextval: reached maximum value of sequence/ ],
+ 'psql watch');
+
+# guess whether it looks like unix enough for testing purposes
+my $is_like_unix = -e "/dev/null" and -d "/tmp" and -x "/bin/cat" and -x "/bin/echo";
+
+# skip un*x-specific zone if it does not look like un*x
+goto END_UNIX_ZONE
+ unless $is_like_unix;
+
+# this probably only works under UN*X-like systems
+psql('', 0, "\\setenv PSQL_EDITOR echo\nSELECT 1 AS one;\n\\e\n\\p\n",
+ [ qr/one.*one/s, qr/SELECT 1/ ], $EMPTY, 'psql edit');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ef regress_psql_tap_1_f1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ef');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ev regress_psql_tap_1_v1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ev');
+psql('', 0, "\\setenv FOO bla\\set foo \`echo \$FOO\`\n\\echo :foo\n",
+ [ qr/bla/ ], $EMPTY, 'psql backtick');
+psql('', 0, "\\setenv FOO blabla\n\\! echo \$FOO\n",
+ [ qr/blabla/ ], $EMPTY, 'psql !');
+
+psql('-o /dev/null', 0, "SELECT 5432 AS pg\n", $EMPTY, $EMPTY, 'psql -o null');
+psql('', 0, "\\o /dev/null\nSELECT 1;\n", $EMPTY, $EMPTY, 'psql \o null');
+psql('', 0, "\\o | cat\nSELECT 5432 AS pg;\n\\o\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql \o cat');
+
+psql('', 0, "SELECT 5432 AS pg;\n\\w /dev/null\n",
+ [ qr/pg.*\b5432\b/s ], $EMPTY, 'psql \w null');
+
+psql('', 0, "SELECT 1\\g /dev/null\n",
+ $EMPTY, $EMPTY, 'psql \g null');
+psql('', 0, "SELECT 5432\\g | cat\n",
+ [ qr/\b5432\n/ ], $EMPTY, 'psql \g cat');
+
+psql('--log-file=/dev/null', 0, "SELECT 5432 AS pg\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql -L null');
+
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) FROM PROGRAM 'echo moe'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy echo');
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) TO PROGRAM 'cat'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy cat'); # :-)
+
+END_UNIX_ZONE:
+
+psql('', 0, "\\i /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \i error');
+psql('', 0, "\\i ~/there/is/no/such/file\n",
+ [ qr{^$} ], [ qr{there/is/no/such/file} ], 'psql \i tilde');
+
+# no such file
+psql('', 0, "\\o /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \o error');
+
+psql('', 0, "\\w /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \w error');
+
+psql('-L /no/such/file', 1, '',
+ [ qr{^$} ], [ qr{could not open log file} ], 'psql -L bad');
+
+# \copy errors
+psql('', 0, "\\copy regress_psql_tap_1_t1 to program '/no/such/prgm'",
+ [ qr/COPY 1/ ], [ qr{/no/such/prgm} ], 'psql copy to bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from program '/no/such/prgm'",
+ [ qr/COPY 0/ ], [ qr{/no/such/prgm} ], 'psql copy from bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 to '/tmp'",
+ $EMPTY, [ qr{directory} ], 'psql copy to tmp');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from '/tmp'",
+ $EMPTY, [ qr/directory/ ], 'psql copy to tmp');
+
+# misc errors
+psql('-P format=bad', 1, '', $EMPTY,
+ [ qr/allowed formats are aligned/, qr/could not set printing parameter/ ], 'psql -P bad');
+psql('-Q', 1, '', $EMPTY, [ qr/Try ".* --help" for more information/ ], 'psql -BAD');
+
+#
+# .psqlrc
+#
+# note: reading the system wide psqlrc cannot be avoided
+#
+my $psqlrc = create_test_file('regress_psql_tap_1_psqlrc', "SELECT 1 AS one\n");
+$ENV{PSQLRC} = $psqlrc;
+psql('!', 0, "SELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], [ qr{^$} ], 'psql .psqlrc');
+delete $ENV{PSQLRC};
+ok(unlink $psqlrc, "unlink $psqlrc");
+
+#
+# large objects
+#
+my $lofile = create_test_file('regress_psql_tap_1_lo', '0123456789ABCDEF' x 1024);
+my $esc_lofile = $lofile;
+$esc_lofile =~ s/'/''/g;
+psql('-H', 0,
+ "\\lo_import '$esc_lofile' 'regress psql TAP lo'\n\\lo_list\n",
+ [ qr{lo_import \d+}, qr{Large objects}, qr{regress psql TAP lo} ], [ qr{^$} ], 'psql lo 1');
+ok(unlink $lofile, "unlink $lofile (1)");
+psql('', 0,
+ "SELECT oid AS loboid\n" .
+ "FROM pg_catalog.pg_largeobject_metadata\n" .
+ "WHERE pg_catalog.obj_description(oid, 'pg_largeobject') = 'regress psql TAP lo'\\gset\n" .
+ "\\echo loboid = :loboid\n" .
+ "\\pset tuples_only on\n" .
+ "SELECT 'lo extract: ' || lo_get(:loboid, 826, 3)::TEXT AS extract;\n" .
+ "\\lo_export :loboid '/no/such/dir/stuff'\n" . # this one fails
+ "\\lo_export :loboid '$esc_lofile'\n" .
+ "\\lo_unlink :loboid\n",
+ [ qr{loboid = \d+}, qr{lo extract: \\x414243} ],
+ [ qr{could not open file "/no/such/dir/stuff"} ],
+ 'psql lo 2');
+ok(-e $lofile, "re-created $lofile");
+ok(unlink $lofile, "unlink $lofile (2)");
+
+#
+# INTERACTIVE STUFF
+#
+# tab-completion checks are not very fast.
+# disable pager to work around pagination interactions.
+
+# basic test
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ "\\echo hello world\n", qr/hello world.*postgres=\# /s ],
+ [ "help\n", qr/You are using.*\\copyright.*quit.*postgres=\# /s ],
+ [ "SELECT\nhelp\n\\r\n", qr/help or press control-[CD].*postgres=\# /s ],
+ [ "SELECT\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT (\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT \$\$\nquit\n\$\$\\r\n", qr/Use control-[CD] to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT '\nquit'\\r\n", qr/quit'.*postgres=\# /s ],
+ [ "SELECT '\n\\q\n", qr/Use control-[CD] to quit.*postgres'\# /s ],
+ [ "'\\r\n", qr/buffer reset.*postgres=\# /s ],
+ [ "SELECT 1\n\\p\n\\r\n", qr/SELECT 1.*SELECT 1.*buffer reset.*postgres=\#/s ],
+ #[ "\\prompt tmp_var\nfoobla\n\\echo hello :tmp_var\n\\unset foo\n\\echo world :tmp_var\n",
+ # qr/hello foobla.*world :tmp_var.*postgres=\# /s ],
+ [ "\\password\n", qr/Enter new password: / ],
+ [ "foo\n", qr/Enter it again: / ],
+ [ "bla\n", qr/Passwords didn't match.*postgres=\# /s ],
+ [ "exit\n" ],
+ ],
+ 'ipsql basic', 1);
+
+# prompt
+ipsql('-P pager', 0, 5,
+ [ # default prompts
+ [ "SELECT 1 +\n", qr/postgres-\# / ],
+ [ "(2 * \n", qr/postgres\(\# / ],
+ [ "3) + '\n", qr/postgres\'\# / ],
+ [ "4' + \$\$\n", qr/postgres\$\# /],
+ [ "5\$\$ AS \"\n", qr/postgres\"\# / ],
+ [ "seize\" /*\n", qr/postgres\*\# / ],
+ [ " ignored */;\n", qr/\bseize\b.*\b16\b.*postgres=\# /s ],
+ [ "\\if false\n", qr/postgres\@\# / ],
+ [ "\\echo IGNORED\n", qr/command ignored.*if block.*postgres\@\# /s ],
+ # BUG: prompt with - instead of @
+ [ "SELECT 1;\n", qr/query ignored.*if block.*postgres.\# /s ],
+ [ "\\endif\n", qr/postgres=\# / ],
+ [ "\\set SINGLELINE ON\n", qr/postgres\^\# / ],
+ [ "\\set SINGLELINE OFF\n", qr/postgres=\# / ],
+ [ "COPY regress_psql_tap_1_t1(data) FROM STDIN;\n", qr/>> / ],
+ [ "hello\nworld\n\\.\n", qr/COPY 2\b.*postgres=\# /s ],
+ # set new prompts (not tested: %`)
+ [ "\\set PROMPT1 '%%%[%]%?%M%m%>%n%/%~%#%p%R%x%l%:QUIET:%040'\n", qr/%.*\d+(on|off) /s ],
+ [ "\\set PROMPT1 '1> '\n\\set PROMPT2 '2> '\n\\set PROMPT3 '3> '\n", qr/\b1> / ],
+ [ "COPY regress_psql_tap_1_t1(data)\n", qr/\b2> / ],
+ [ "FROM STDIN;\n", qr/\b3> / ],
+ [ "calvin\nhobbes\nsusie\nmoe\n\\.\n", qr/COPY 4\b.*\b1> /s ],
+ # reset prompts
+ [ "\\set PROMPT1 '%/%R%# '\n", qr/PROMPT1.*postgres=# /s ],
+ [ "\\set PROMPT2 '%/%R%# '\n", qr/PROMPT2.*postgres=# /s ],
+ [ "\\set PROMPT3 '>> '\n", qr/PROMPT3.*postgres=# /s ],
+ ],
+ 'ipsql prompt 1');
+
+goto END_HISTORY_ZONE
+ unless $has_history;
+
+# signals
+my $stop = $is_like_unix ? "\004" : "\003";
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ $stop, qr/\\q/ ],
+ ],
+ 'ipsql signals 1', 1);
+
+psql('', 0, "\\s /dev/null\n", $EMPTY, $EMPTY, 'psql \s null');
+
+# tab-complation
+ipsql('-P pager', 0, 5,
+ [ # commands
+ [ "SEL\t", qr/SELECT / ],
+ [ "* FROM pg_catalog.pg_ca\t", qr/pg_catalog\.pg_cast / ],
+ [ "LIMIT 17;\n", qr/\b17 rows.*postgres=\# /s ],
+ # backslash commands
+ [ "\\d\t", qr/(\\da.*\\dy.*postgres=# )?\\d/s ],
+ [ "y\n", qr/List of event triggers.*postgres=\# /s ],
+ # pset
+ [ "\\pset \t", qr/(unicode_column_linestyle.*postgres=\# )?\\pset/s ],
+ [ "tu\t", qr/tuples_only / ],
+ [ "off\n", qr/postgres=\# / ],
+ [ "\\pset unicode_b\t", qr/unicode_border_linestyle / ],
+ [ "\t", qr/(double.*single.*postgres=\# )?/s ],
+ [ "double\n", qr/Unicode border line style is "double".*postgres=\# /s ],
+ # set
+ [ "\\set VERB\t", qr/VERBOSITY / ],
+ [ "\t", qr/(default.*verbose.*postgres=\# )?/s ],
+ [ "d\t\n", qr/default.*postgres=\# /s ],
+ # misc
+ [ "\\cd \t", qr/(.*postgres=\# )?\\cd /s ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # object type completion,
+ # strange: the list is not seen from the test driver
+ # but the coverage works as expected.
+ #[ "CREATE \t", qr/i(MATERIALIZED VIEW.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "CREATE \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "DROP \t", qr/(UNLOGGED.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "DROP \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "ALTER \t", qr/(TABLESPACE.*postgres=\# )/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ALTER \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ANALYZE (\t", qr/(SKIP_LOCKED.*postgres=\# )?ANALYZE \(/s ],
+ [ "V\tOF\t", qr/VERBOSE OFF/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # SET
+ [ "SET \t", qr/(zero_damaged_pages.*postgres=\# )?SET /s ],
+ [ "da\t", qr/datestyle / ],
+ [ "\t", qr/TO / ],
+ #[ "\t", qr/(YMD.*postgres=\# SET datestyle TO )?/s ],
+ [ "\tDE\t", qr/DEFAULT/ ],
+ [ ";\n", qr/SET.*postgres=\# /s ],
+ # SET completion on a GUC variable,
+ #[ "SET default_with\t", qr/default_with_oids / ],
+ #[ "TO \t", qr/(YES.*postgres=\# SET default_with_oids )?TO /s ],
+ #[ "D\t", qr/DEFAULT/ ],
+ #[ ";\n", qr/SET.*postgres=\# / ],
+ # psql variables
+ [ "\\ec\t", qr/\\echo / ],
+ [ ":q\t", qr/QUIET/ ],
+ [ ":'PO\t", qr/:'PORT'/ ],
+ [ ":\"HISTC\t", qr/:"HISTCONTROL"/ ],
+ [ "\n", qr/(on|off).*'\d+'.*"[a-z]+".*postgres=\# /s ],
+ # file
+ [ "COPY regress_psql_tap_1_t1 FROM \t", qr/(postgres=\# COPY .* FROM )?/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ ],
+ 'ipsql tab 1');
+
+END_HISTORY_ZONE:
+
+# final cleanup
+psql('', 0, '-- cleanup objects
+DROP VIEW regress_psql_tap_1_v1;
+DROP FUNCTION regress_psql_tap_1_f1;
+DROP TABLE regress_psql_tap_1_t1;
+DROP USER regress_psql_tap_1;
+', [ qr{DROP VIEW}, qr{DROP FUNCTION}, qr{DROP TABLE}, qr{DROP ROLE} ], [ qr{^$} ], 'psql -- drop stuff');
+
+$node->stop();
+done_testing();
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index b685b35282..2692c99e17 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -92,6 +92,7 @@ $node->issues_sql_like(
$node->command_checks_all(
[ 'vacuumdb', '--analyze', '--table', 'vacview', 'postgres' ],
0,
+ '',
[qr/^.*vacuuming database "postgres"/],
[qr/^WARNING.*cannot vacuum non-tables or special system tables/s],
'vacuumdb with view');
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 270bd6c856..9c28bec4a7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1676,6 +1676,28 @@ sub command_checks_all
=pod
+=item $node->icommand_checks(cmd, ...)
+
+TestLib::icommand_checks with our connection parameters.
+See command_ok(...)
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local $ENV{PGHOST} = $self->host;
+ local $ENV{PGPORT} = $self->port;
+
+ TestLib::icommand_checks(@_);
+ return;
+}
+
+=pod
+
=item $node->issues_sql_like(cmd, expected_sql, test_name)
Run a command on the node, then verify that $expected_sql appears in the
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 92199792eb..1b2f7d0cfb 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -53,6 +53,7 @@ use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
+use Expect;
# specify a recent enough version of Test::More to support the
# done_testing() function
@@ -81,6 +82,7 @@ our @EXPORT = qw(
command_like_safe
command_fails_like
command_checks_all
+ icommand_checks
$windows_os
);
@@ -792,6 +794,8 @@ Arguments:
=item C<ret>: Expected exit code
+=item C<in>: Stdin for command
+
=item C<out>: Expected stdout from command
=item C<err>: Expected stderr from command
@@ -806,12 +810,13 @@ sub command_checks_all
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($cmd, $expected_ret, $out, $err, $test_name) = @_;
+ my ($cmd, $expected_ret, $in, $out, $err, $test_name) = @_;
+ $in = '' if not defined $in;
# run command
my ($stdout, $stderr);
print("# Running: " . join(" ", @{$cmd}) . "\n");
- IPC::Run::run($cmd, '>', \$stdout, '2>', \$stderr);
+ IPC::Run::run($cmd, '<', \$in, '>', \$stdout, '2>', \$stderr);
# See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
my $ret = $?;
@@ -840,6 +845,84 @@ sub command_checks_all
=pod
+=item icommand_checks(cmd, ret, timeout, init, inout, name)
+
+Run a command and check its status and outputs.
+Arguments:
+
+=over
+
+=item C<cmd> ref to list for command, options and arguments to run
+
+=item C<ret> expected exit status
+
+=item C<timeout> for interactions
+
+=item C<init> initial output before interacting
+
+=item C<inout> [ [ input, list of output checks ], ... ]
+
+=item C<name> of the test
+
+=back
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($cmd, $expected_ret, $timeout, $init, $inout, $name) = @_;
+
+ my $ps = Expect->spawn(@$cmd);
+ ok(defined $ps, "$name command spawn: @$cmd");
+ return unless defined $ps;
+
+ #$ps->slave->stty(qw(raw -echo));
+ $ps->slave->stty(qw(raw));
+ my $n = 0;
+ for my $test (@$inout)
+ {
+ #warn "test: @$test";
+ my ($in, @out) = @$test;
+ $n++;
+ #warn "in: $in";
+ #warn "out: @out";
+ $ps->send($in);
+ for my $o (@out)
+ {
+ my $ok = $ps->expect($timeout, '-re', $o);
+ # check header on first output
+ if (defined $init)
+ {
+ # beware of race conditions...
+ ok($ps->before =~ $init,
+ "$name header matches /$init/");
+ undef $init;
+ }
+ ok($ok, "$n: $name output matches /$o/");
+ if (not $ok)
+ {
+ print STDERR "last input: ###$in####\n";
+ print STDERR "before: ###" . $ps->before . "###\n" if $ps->before;
+ print STDERR "match: ###" . $ps->match . "###\n" if $ps->match;
+ print STDERR "after: ###" . $ps->after . "###\n" if $ps->after;
+ print STDERR "error: ###" . $ps->error . "###\n" if $ps->error;
+ }
+ }
+ }
+ $ps->soft_close();
+
+ # check status
+ my $ret = $ps->exitstatus;
+ ok($ret == $expected_ret,
+ "$name status (got $ret vs expected $expected_ret)");
+
+ return;
+}
+
+=pod
+
=back
=cut
On Thu, Sep 12, 2019 at 11:56 AM Fabien COELHO <coelho@cri.ensmp.fr> wrote:
On Wed, Sep 11, 2019 at 10:52:01PM +0200, Fabien COELHO wrote:
AFAICR this is because the coverage was not the same:-) Some backslash
commands just skip silently to the end of the line, so that intermediate
\commands on the same line are not recognized/processed the same, so I moved
everything on one line to avoid this.I see. So basically this tests for more code paths to ignore
backslash commands and it improves the coverage of \elif. Applied
after fixing two nits:
- Indentation was incorrect.
- Moved the \elif test closer to the existing one for the false
branch (you can grep #2 to find it).Ok. Rebased version added, with some minor changes to improve readability
(comments, variables).
Few comments:
+sub create_test_file
+{
+ my ($fname, $contents) = @_;
+ my $fn = $node->basedir . '/' . $fname;
+ #ok(not -e $fn, "$fn must not already exists");
+ append_to_file($fn, $contents);
+ return $fn;
+}
Commented line can be removed
+# nope, interacts on tty
+#psql('-W', 0, "foo\n", [ qr{^$} ], [ qr{^$} ], 'psql -W');
+psql('-x', 0, "SELECT 1 AS one, 2 AS two;\n", [ qr{one \| 1.*two \|
2}s ], $EMPTY, 'psql -x');
+# some issue, \0 is not welcome somewhere
+#psql('-A -z', "SELECT 1 AS one, 2 AS two;\n", [ qr{one.two}s,
qr{1.2}s ], $EMPTY, 'psql -z');
+#psql('-A -0', "SELECT 1 AS one, 2 AS two;\n", [ qr{two.1}s ],
$EMPTY, 'psql -0');
+psql('-1', 0, "SELECT 54;\nSELECT 32;\n", [ qr{54}, qr{32} ], $EMPTY,
'psql -1');
Commented lines can be removed
+ [ "\\lo_list\n", [ qr{Large objects} ] ],
+ [ "\\if true\\q\\endif\n", $EMPTY ],
+ # ???
+ #[ "SELECT md5('hello world');\n\\s\n", [ qr{5eb63bbbe0}, qr{SELECT md5} ] ],
+ [ "\\set\n", [ qr{ENCODING = }, qr{VERSION_NUM = } ] ],
+ [ "\\set COMP_KEYWORD_CASE preserve-lower\n\\set COMP_KEYWORD_CASE lower\n" .
#[ "Select"] commented line can be removed
??? can be changed to some suitable heading
+psql('', 0, "\\s /dev/null\n", $EMPTY, $EMPTY, 'psql \s null');
+
+# tab-complation
+ipsql('-P pager', 0, 5,
+ [ # commands
tab-complation to be changed to tab-completion
+ # but the coverage works as expected.
+ #[ "CREATE \t", qr/i(MATERIALIZED VIEW.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "CREATE \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "DROP \t", qr/(UNLOGGED.*postgres=\# )?/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "DROP \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ #[ "ALTER \t", qr/(TABLESPACE.*postgres=\# )/s ],
+ #[ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
Commented lines can be removed, some more are present below these lines also.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Ok. Rebased version added, with some minor changes to improve readability
(comments, variables).Few comments: [...]
Commented line can be removed
Commented lines can be removed
??? can be changed to some suitable heading
tab-complation to be changed to tab-completion
Commented lines can be removed, some more are present below these lines also.
Thanks for this review.
The lines were really tests I did that had some issues because of the way
the Expect module works, and are not useful for inclusion in the code
base.
Here is a v5.
--
Fabien Coelho - CRI, MINES ParisTech
Attachments:
psql-tap-5.patchtext/x-diff; name=psql-tap-5.patchDownload
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index b7d36b65dd..aabc27ab6f 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -506,6 +506,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*checksum verification failed/s],
'pg_basebackup reports checksum mismatch');
@@ -526,6 +527,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt2" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*further.*failures.*will.not.be.reported/s],
'pg_basebackup does not report more than 5 checksum mismatches');
@@ -542,6 +544,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt3" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*7 total checksum verification failures/s],
'pg_basebackup correctly report the total number of checksum mismatches');
diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl
index 59228b916c..cf4811d382 100644
--- a/src/bin/pg_checksums/t/002_actions.pl
+++ b/src/bin/pg_checksums/t/002_actions.pl
@@ -62,6 +62,7 @@ sub check_relation_corruption
'--filenode', $relfilenode_corrupted
],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data for single relfilenode on tablespace $tablespace"
@@ -71,6 +72,7 @@ sub check_relation_corruption
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data on tablespace $tablespace");
@@ -203,6 +205,7 @@ sub fail_corrupt
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/^$/],
[qr/could not read block 0 in file.*$file\":/],
"fails for corrupted data in $file");
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
index 3b63ad230f..ebe9b80a52 100644
--- a/src/bin/pg_controldata/t/001_pg_controldata.pl
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -33,6 +33,7 @@ close $fh;
command_checks_all(
[ 'pg_controldata', $node->data_dir ],
0,
+ '',
[
qr/WARNING: Calculated CRC checksum does not match value stored in file/,
qr/WARNING: invalid WAL segment size/
diff --git a/src/bin/pg_resetwal/t/002_corrupted.pl b/src/bin/pg_resetwal/t/002_corrupted.pl
index f9940d7fc5..1990669d26 100644
--- a/src/bin/pg_resetwal/t/002_corrupted.pl
+++ b/src/bin/pg_resetwal/t/002_corrupted.pl
@@ -30,6 +30,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/
@@ -46,6 +47,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index b82d3f65c4..01010914fe 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -50,7 +50,7 @@ sub pgbench
push @cmd, @args;
- $node->command_checks_all(\@cmd, $stat, $out, $err, $name);
+ $node->command_checks_all(\@cmd, $stat, '', $out, $err, $name);
# cleanup?
#unlink @filenames or die "cannot unlink files (@filenames): $!";
diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl
index f7fa18418b..b58f3548c3 100644
--- a/src/bin/pgbench/t/002_pgbench_no_server.pl
+++ b/src/bin/pgbench/t/002_pgbench_no_server.pl
@@ -25,7 +25,7 @@ sub pgbench
my ($opts, $stat, $out, $err, $name) = @_;
print STDERR "opts=$opts, stat=$stat, out=$out, err=$err, name=$name";
command_checks_all([ 'pgbench', split(/\s+/, $opts) ],
- $stat, $out, $err, $name);
+ $stat, '', $out, $err, $name);
return;
}
@@ -52,7 +52,7 @@ sub pgbench_scripts
push @cmd, '-f', $filename;
}
}
- command_checks_all(\@cmd, $stat, $out, $err, $name);
+ command_checks_all(\@cmd, $stat, '', $out, $err, $name);
return;
}
diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore
index c2862b12d6..d324c1c1fa 100644
--- a/src/bin/psql/.gitignore
+++ b/src/bin/psql/.gitignore
@@ -3,3 +3,4 @@
/sql_help.c
/psql
+/tmp_check
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 69bb297fe7..9473ab01cb 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -60,8 +60,15 @@ uninstall:
clean distclean:
rm -f psql$(X) $(OBJS) lex.backup
+ rm -rf tmp_check
# files removed here are supposed to be in the distribution tarball,
# so do not clean them in the clean/distclean rules
maintainer-clean: distclean
rm -f sql_help.h sql_help.c psqlscanslash.c
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/psql/t/001_psql.pl b/src/bin/psql/t/001_psql.pl
new file mode 100644
index 0000000000..e1d1add824
--- /dev/null
+++ b/src/bin/psql/t/001_psql.pl
@@ -0,0 +1,856 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+my $node = get_new_node('main');
+$node->init();
+$node->start();
+
+# create a file under the test directory
+# return its full path
+sub create_test_file
+{
+ my ($fname, $contents) = @_;
+ my $fn = $node->basedir . '/' . $fname;
+ append_to_file($fn, $contents);
+ return $fn;
+}
+
+# invoke psql
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected exit status
+# - in: input stream
+# - out: list of re to check on stdout
+# - err: list of re to check on stderr
+# - name: of the test
+# - more: more raw arguments
+sub psql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $in, $out, $err, $name, @more) = @_;
+ $opts =~ s/^!// or push @more, '-X';
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+ $node->command_checks_all(\@cmd, $stat, $in, $out, $err, $name);
+ return;
+}
+
+# invoke psql interactively, making it believe it is connected to a tty
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected final exit status
+# - timeout: for interactions, in second
+# - inout: list of input expected re
+# - name: of the test
+# - remove: tell to remove init/start/end checks
+sub ipsql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $timeout, $inout, $name, $remove) = @_;
+
+ # build command to run
+ my @more = ();
+ @more = ('-X') unless $opts =~ /^!/;
+ $opts =~ s/^!//;
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+
+ # check it is running at start and end, and exit cleanly
+ unless ($remove)
+ {
+ unshift @$inout, [ "\\echo STARTING :VERSION_NUM\n", qr/STARTING \d+.*postgres=\# /s ];
+ push @$inout, [ "\\echo ENDING :VERSION_NUM\\q\n", qr/ENDING \d+$/s ];
+ }
+
+ # call the stuff
+ $node->icommand_checks(
+ \@cmd, $stat, $timeout,
+ $remove ? undef : qr/psql .*Type "help" for help..*postgres=# /s,
+ $inout, $name);
+ return;
+}
+
+#
+# check whether we have readline/history capability
+#
+my ($out, $err);
+$node->psql('postgres', "\\s\n", 'stdout' => \$out, 'stderr' => \$err);
+my $has_history = $err !~ /history is not supported by this installation/;
+
+my $EMPTY = [ qr/^$/ ];
+
+#
+# check various options
+#
+# this cannot be tested from within a SQL script
+psql('!--help', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql --help');
+psql('!-?', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql -?');
+psql('!--version', 0, '',
+ [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql --version');
+psql('--help=variables', 0, '',
+ [ qr{VERSION_NAME}, qr{unicode_border_linestyle}, qr{PSQL_EDITOR} ],
+ $EMPTY, 'psql --help=variables');
+# look for plenty of backslash-commands
+psql('--help=commands', 0, '',
+ [ qr{\\copyright}, qr{\\pset}, qr{(?!VERSION_NAME)} ],
+ $EMPTY, 'psql --help=commands');
+
+# tested elsewhere: -a -A -d -e -h -L
+psql('-b', 0, "SELECT 1/0;\n",
+ $EMPTY, [ qr{STATEMENT: SELECT 1/0}, qr{ERROR: division by zero} ], 'psql -b');
+psql('-c \\q', 0, '', $EMPTY, $EMPTY, 'psql -c');
+psql('-c \\l -c \\l', 0, '', [ qr{template0.*template0}s ], $EMPTY, 'psql -c -c');
+psql('', 0, '', [ qr{(?!1234)}, qr{5678}s ], $EMPTY, 'psql -c ;;', ('-c', 'SELECT 1234; SELECT 5678;'));
+psql('-E', 0, "\\l\n", [ qr{\* QUERY \*}, qr{template0} ], $EMPTY, 'psql -E');
+psql('-A -F _', 0, "SELECT 54, 32;\n", [ qr{54_32} ], $EMPTY, 'psql -F _');
+psql('-H', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -H');
+psql('-l', 0, '', [ qr{template0}, qr{template1} ], $EMPTY, 'psql -l');
+psql('-n', 0, "\\q\n", $EMPTY, $EMPTY, 'psql -n');
+psql('-P format=html', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -P format=html');
+psql('-P tuples_only', 0, "\\pset\n", [ qr{tuples_only.*on} ], $EMPTY, 'psql -P tuples_only');
+psql('-A -R_', 0, "VALUES (1), (2);\n", [ qr{_1_2_} ], $EMPTY, 'psql -R _');
+psql('-s', 0, "SELECT 1;\n", [ qr{verify command}, qr{SELECT 1}, qr{press return to proceed} ], $EMPTY, 'psql -s');
+psql('-S', 0, "SELECT 1 AS one\nSELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], $EMPTY, 'psql -S');
+psql('-H -T width=100', 0, "SELECT 1 AS one;\n", [ qr{table.*width=100} ], $EMPTY, 'psql -T ...');
+psql('-X -V', 0, '', [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql -X -V');
+psql('', 0, "\\echo :VERSION_NUM\n", [ qr{\d{6}} ], $EMPTY, 'psql -- show VERSION_NUM');
+psql('-v VERSION_NUM', 0, "\\echo :VERSION_NUM\n", [ qr{:VERSION_NUM} ], $EMPTY, 'psql -v ...');
+psql('-v VERSION_NUM=bla', 0, "\\echo :VERSION_NUM\n", [ qr{bla} ], $EMPTY, 'psql -v ...=.');
+psql('-w', 0, '', $EMPTY, $EMPTY, 'psql -w');
+psql('-x', 0, "SELECT 1 AS one, 2 AS two;\n", [ qr{one \| 1.*two \| 2}s ], $EMPTY, 'psql -x');
+psql('-1', 0, "SELECT 54;\nSELECT 32;\n", [ qr{54}, qr{32} ], $EMPTY, 'psql -1');
+psql('-X -?', 0, '',
+ [ qr{interactive terminal}, qr{Usage:}, qr{Connection options:} ], $EMPTY, 'psql -X -?');
+psql('--csv', 0, "SELECT 1 AS one, 2 AS two\n", [ qr{one,two}, qr{1,2} ], $EMPTY, 'psql --csv');
+
+#
+# create some objects to help \d tests later
+#
+psql('', 0, '-- create some objects
+CREATE USER regress_psql_tap_1;
+ALTER ROLE regress_psql_tap_1 SET enable_indexscan TO off;
+CREATE TABLE regress_psql_tap_1_t1(data TEXT NOT NULL);
+CREATE FUNCTION regress_psql_tap_1_f1() RETURNS INTEGER AS $$ SELECT 54321 $$ LANGUAGE SQL;
+CREATE VIEW regress_psql_tap_1_v1 AS SELECT 54321 AS one;
+', [ qr{CREATE ROLE}, qr{ALTER ROLE} ], [ qr{^$} ], 'psql -- create stuff');
+
+#
+# check interactive help
+#
+# this is mostly for coverage, including occasional negatives.
+# we certainly do not want to have an output file with the whole doc.
+my @backslash_help = (
+ # \h command, list of stdout matches
+ [ 'no-help', [ qr{No help available for} ] ],
+ [ '' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ [ ' ' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ # lists
+ [ 'ALTER', [ qr{AGGREGATE}, qr{ROLE}, qr{VIEW}, qr{(?!ACCESS METHOD)}, qr{(?!TRANSFORM)} ] ],
+ [ 'ALTER TEXT SEARCH', [ qr{CONFIGURATION}, qr{DICTIONARY}, qr{PARSER}, qr{TEMPLATE} ] ],
+ [ 'ALTER FOREIGN', [ qr{DATA WRAPPER}, qr{TABLE} ] ],
+ [ 'CREATE', [ qr{ACCESS METHOD}, qr{TRANSFORM} ] ],
+ [ 'DROP', [ qr{ACCESS METHOD}, qr{VIEW} ] ],
+ # details
+ [ 'ABORT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'ALTER AGGREGATE', [ qr{RENAME TO}, qr{(?!CONVERSION)} ] ],
+ [ 'ALTER CONVERSION', [ qr{SET SCHEMA}, qr{(?!AGGREGATE)} ] ],
+ [ 'ALTER COLLATION', [ qr{REFRESH VERSION} ] ],
+ [ 'ALTER DATABASE', [ qr{CONNECTION LIMIT} ] ],
+ [ 'ALTER DEFAULT PRIVILEGES', [ qr{GRANT} ] ],
+ [ 'ALTER DOMAIN', [ qr{VALIDATE CONSTRAINT} ] ],
+ [ 'ALTER EVENT TRIGGER', [ qr{DISABLE} ] ],
+ [ 'ALTER EXTENSION', [ qr{OPERATOR FAMILY} ] ],
+ [ 'ALTER FOREIGN DATA WRAPPER', [ qr{VALIDATOR} ] ],
+ [ 'ALTER FOREIGN TABLE', [ qr{SET STORAGE} ] ],
+ [ 'ALTER FUNCTION', [ qr{PARALLEL} ] ],
+ [ 'ALTER GROUP', [ qr{DROP USER} ] ],
+ [ 'ALTER INDEX', [ qr{ALL IN TABLESPACE} ] ],
+ [ 'ALTER LANGUAGE', [ qr{PROCEDURAL}, qr{(?!INDEX)} ] ],
+ [ 'ALTER LARGE OBJECT', [ qr{OWNER TO} ] ],
+ [ 'ALTER MATERIALIZED VIEW', [ qr{SET TABLESPACE} ] ],
+ [ 'ALTER OPERATOR', [ qr{JOIN =} ] ],
+ [ 'ALTER OPERATOR CLASS', [ qr{SET SCHEMA}, qr{(?!LANGUAGE)} ] ],
+ [ 'ALTER OPERATOR FAMILY', [ qr{FUNCTION} ] ],
+ [ 'ALTER POLICY', [ qr{WITH CHECK} ] ],
+ [ 'ALTER PROCEDURE', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER PUBLICATION', [ qr{DROP TABLE} ] ],
+ [ 'ALTER ROLE', [ qr{BYPASSRLS} ] ],
+ [ 'ALTER ROUTINE', [ qr{LEAKPROOF} ] ],
+ [ 'ALTER RULE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER SCHEMA', [ qr{OWNER TO} ] ],
+ [ 'ALTER SEQUENCE', [ qr{NO MAXVALUE} ] ],
+ [ 'ALTER SERVER', [ qr{VERSION} ] ],
+ [ 'ALTER STATISTICS', [ qr{SET SCHEMA} ] ],
+ [ 'ALTER SUBSCRIPTION', [ qr{REFRESH PUBLICATION} ] ],
+ [ 'ALTER SYSTEM', [ qr{RESET ALL} ] ],
+ [ 'ALTER TABLE', [ qr{ALL IN TABLESPACE}, qr{DISABLE ROW LEVEL SECURITY} ] ],
+ [ 'ALTER TABLESPACE', [ qr{RESET} ] ],
+ [ 'ALTER TEXT SEARCH CONFIGURATION', [ qr{ALTER MAPPING FOR} ] ],
+ [ 'ALTER TEXT SEARCH DICTIONARY', [ qr{OWNER TO} ] ],
+ [ 'ALTER TEXT SEARCH PARSER', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TEXT SEARCH TEMPLATE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TRIGGER', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER TYPE', [ qr{RENAME ATTRIBUTE} ] ],
+ [ 'ALTER USER', [ qr{NOREPLICATION}, qr{(?!MAPPING)} ] ],
+ [ 'ALTER USER MAPPING', [ qr{SERVER} ] ],
+ [ 'ALTER VIEW', [ qr{DROP DEFAULT} ] ],
+ [ 'ANALYZE', [ qr{VERBOSE}, qr{SKIP_LOCKED} ] ],
+ [ 'BEGIN', [ qr{ISOLATION LEVEL} ] ],
+ [ 'CALL', [] ],
+ [ 'CHECKPOINT', [] ],
+ [ 'CLOSE', [ qr{ALL} ] ],
+ [ 'CLUSTER', [ qr{VERBOSE} ] ],
+ [ 'COMMENT', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'COMMIT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'COMMIT PREPARED', [] ],
+ [ 'COPY', [ qr{FORMAT}, qr{FORCE_NOT_NULL} ] ],
+ [ 'CREATE ACCESS METHOD', [ qr{HANDLER} ] ],
+ [ 'CREATE AGGREGATE', [ qr{SSPACE}, qr{SORTOP}, qr{FINALFUNC} ] ],
+ [ 'CREATE CAST', [ qr{AS IMPLICIT} ] ],
+ [ 'CREATE COLLATION', [ qr{LC_CTYPE}, qr{DETERMINISTIC} ] ],
+ [ 'CREATE CONVERSION', [ qr{FOR} ] ],
+ [ 'CREATE DATABASE', [ qr{ALLOW_CONNECTIONS} ] ],
+ [ 'CREATE DOMAIN', [ qr{COLLATE} ] ],
+ [ 'CREATE EVENT TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE EXTENSION', [ qr{FROM} ] ],
+ [ 'CREATE FOREIGN DATA WRAPPER', [ qr{NO VALIDATOR} ] ],
+ [ 'CREATE FOREIGN TABLE', [ qr{SERVER} ] ],
+ [ 'CREATE FUNCTION', [ qr{CALLED ON NULL INPUT}, qr{(?!PROCEDURE)} ] ],
+ [ 'CREATE GROUP', [ qr{ADMIN} ] ],
+ [ 'CREATE INDEX', [ qr{CONCURRENTLY}, qr{WHERE} ] ],
+ [ 'CREATE LANGUAGE', [ qr{TRUSTED} ] ],
+ [ 'CREATE MATERIALIZED VIEW', [ qr{DATA} ] ],
+ [ 'CREATE OPERATOR', [ qr{LEFTARG} ] ],
+ [ 'CREATE OPERATOR CLASS', [ qr{FOR ORDER BY} ] ],
+ [ 'CREATE OPERATOR FAMILY', [ qr{USING} ] ],
+ [ 'CREATE POLICY', [ qr{PERMISSIVE} ] ],
+ [ 'CREATE PROCEDURE', [ qr{SECURITY INVOKER}, qr{(?!FUNCTION)} ] ],
+ [ 'CREATE PUBLICATION', [ qr{FOR ALL TABLES} ] ],
+ [ 'CREATE ROLE', [ qr{SYSID} ] ],
+ [ 'CREATE RULE', [ qr{ALSO} ] ],
+ [ 'CREATE SCHEMA', [ qr{AUTHORIZATION} ] ],
+ [ 'CREATE SEQUENCE', [ qr{NO MINVALUE} ] ],
+ [ 'CREATE SERVER', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'CREATE STATISTICS', [ qr{FROM} ] ],
+ [ 'CREATE SUBSCRIPTION', [ qr{CONNECTION} ] ],
+ [ 'CREATE TABLE', [ qr{PARTITION BY} ] ],
+ [ 'CREATE TABLE AS', [ qr{ON COMMIT} ] ],
+ [ 'CREATE TABLESPACE', [ qr{LOCATION} ] ],
+ [ 'CREATE TEXT SEARCH CONFIGURATION', [ qr{PARSER} ] ],
+ [ 'CREATE TEXT SEARCH DICTIONARY', [ qr{TEMPLATE} ] ],
+ [ 'CREATE TEXT SEARCH PARSER', [ qr{GETTOKEN} ] ],
+ [ 'CREATE TEXT SEARCH TEMPLATE', [ qr{LEXIZE} ] ],
+ [ 'CREATE TRANSFORM', [ qr{FROM SQL WITH FUNCTION} ] ],
+ [ 'CREATE TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE TYPE', [ qr{SUBTYPE} ] ],
+ [ 'CREATE USER', [ qr{PASSWORD NULL} ] ],
+ [ 'CREATE USER MAPPING', [ qr{SERVER} ] ],
+ [ 'CREATE VIEW', [ qr{CASCADED} ] ],
+ [ 'DEALLOCATE', [ qr{PREPARE} ] ],
+ [ 'DECLARE', [ qr{INSENSITIVE} ] ],
+ [ 'DELETE', [ qr{WHERE CURRENT OF} ] ],
+ [ 'DISCARD', [ qr{PLANS} ] ],
+ [ 'DO', [ qr{LANGUAGE} ] ],
+ [ 'DROP ACCESS METHOD', [ qr{CASCADE} ] ],
+ [ 'DROP AGGREGATE', [ qr{ORDER BY} ] ],
+ [ 'DROP CAST', [ qr{RESTRICT} ] ],
+ [ 'DROP COLLATION', [ qr{RESTRICT} ] ],
+ [ 'DROP CONVERSION', [ qr{RESTRICT} ] ],
+ [ 'DROP DATABASE', [ qr{IF EXISTS} ] ],
+ [ 'DROP DOMAIN', [ qr{RESTRICT} ] ],
+ [ 'DROP EVENT TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP EXTENSION', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN DATA WRAPPER', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP FUNCTION', [ qr{RESTRICT} ] ],
+ [ 'DROP GROUP', [ qr{IF EXISTS} ] ],
+ [ 'DROP INDEX', [ qr{RESTRICT} ] ],
+ [ 'DROP LANGUAGE', [ qr{RESTRICT} ] ],
+ [ 'DROP MATERIALIZED VIEW', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR CLASS', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR FAMILY', [ qr{RESTRICT} ] ],
+ [ 'DROP OWNED', [ qr{CURRENT_USER} ] ],
+ [ 'DROP POLICY', [ qr{RESTRICT} ] ],
+ [ 'DROP PROCEDURE', [ qr{RESTRICT} ] ],
+ [ 'DROP PUBLICATION', [ qr{RESTRICT} ] ],
+ [ 'DROP ROLE', [ qr{IF EXISTS} ] ],
+ [ 'DROP ROUTINE', [ qr{RESTRICT} ] ],
+ [ 'DROP RULE', [ qr{RESTRICT} ] ],
+ [ 'DROP SCHEMA', [ qr{RESTRICT} ] ],
+ [ 'DROP SEQUENCE', [ qr{RESTRICT} ] ],
+ [ 'DROP SERVER', [ qr{RESTRICT} ] ],
+ [ 'DROP STATISTICS', [ qr{IF EXISTS} ] ],
+ [ 'DROP SUBSCRIPTION', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLESPACE', [ qr{IF EXISTS} ] ],
+ [ 'DROP TEXT SEARCH CONFIGURATION', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH DICTIONARY', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH PARSER', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH TEMPLATE', [ qr{RESTRICT} ] ],
+ [ 'DROP TRANSFORM', [ qr{LANGUAGE} ] ],
+ [ 'DROP TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP TYPE', [ qr{RESTRICT} ] ],
+ [ 'DROP USER', [ qr{IF EXISTS} ] ],
+ [ 'DROP USER MAPPING', [ qr{SERVER} ] ],
+ [ 'DROP VIEW', [ qr{RESTRICT} ] ],
+ [ 'END', [ qr{TRANSACTION}, qr{CHAIN}, qr{(?!COMMIT)} ] ],
+ [ 'EXECUTE', [ ] ],
+ [ 'EXPLAIN', [ qr{VERBOSE} ] ],
+ [ 'FETCH', [ qr{RELATIVE} ] ],
+ [ 'GRANT', [ qr{ALL TABLES} ] ],
+ [ 'IMPORT FOREIGN SCHEMA', [ qr{FROM SERVER} ] ],
+ [ 'INSERT', [ qr{ON CONFLICT} ] ],
+ [ 'LISTEN', [ ] ],
+ [ 'LOAD', [ ] ],
+ [ 'LOCK', [ qr{NOWAIT} ] ],
+ [ 'MOVE', [ qr{FORWARD} ] ],
+ [ 'NOTIFY', [ ] ],
+ [ 'PREPARE', [ qr{AS} ] ],
+ [ 'PREPARE TRANSACTION', [ ] ],
+ [ 'REASSIGN OWNED', [ qr{SESSION_USER} ] ],
+ [ 'REFRESH MATERIALIZED VIEW', [ qr{CONCURRENTLY} ] ],
+ [ 'REINDEX', [ qr{DATABASE} ] ],
+ [ 'RELEASE', [ qr{SAVEPOINT} ] ],
+ [ 'RESET', [ qr{RESET ALL} ] ],
+ [ 'REVOKE', [ qr{PRIVILEGES} ] ],
+ [ 'ROLLBACK', [ qr{CHAIN} ] ],
+ [ 'ROLLBACK PREPARED', [ ]],
+ [ 'SAVEPOINT', [ ] ],
+ [ 'SECURITY LABEL', [ qr{SUBSCRIPTION} ] ],
+ [ 'SELECT', [ qr{RECURSIVE} ] ],
+ [ 'SET', [ qr{SESSION} ] ],
+ [ 'SET CONSTRAINTS', [ qr{IMMEDIATE} ] ],
+ [ 'SET ROLE', [ qr{ROLE NONE} ] ],
+ [ 'SET SESSION AUTHORIZATION', [ qr{DEFAULT} ] ],
+ [ 'SET TRANSACTION', [ qr{SNAPSHOT} ] ],
+ [ 'SHOW', [ qr{SHOW ALL} ] ],
+ [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ],
+ [ 'TABLE', [ qr{ONLY} ] ], # hmmm...
+ [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ],
+ [ 'UNLISTEN', [ ] ],
+ [ 'UPDATE', [ qr{RETURNING} ] ],
+ [ 'VACUUM', [ qr{FREEZE} ] ],
+ [ 'VALUES', [ qr{ORDER BY} ] ],
+ [ 'WITH', [ qr{RECURSIVE} ] ], # SELECT duplicate?
+);
+
+for my $h (@backslash_help)
+{
+ my ($cmd, $out) = @$h;
+ push @$out, qr{$cmd};
+ psql('', 0, "\\h $cmd\n", $out, [ qr{^$} ], "psql -- \\h $cmd");
+}
+
+# special cases
+psql('', 0, "\\h ROLLBACK TO SAVEPOINT\n", [ qr{ROLLBACK}, qr{SAVEPOINT} ],
+ [ qr{^$} ], "psql -- \\h ROLLBACK TO SAVEPOINT");
+psql('', 0, "\\h SELECT INTO\n", [ qr{SELECT}, qr{INTO} ],
+ [ qr{^$} ], "psql -- \\h SELECT INTO");
+
+#
+# check describe and other backslash commands
+#
+# the output can vary significantly, especially with +
+my @backslash_out = (
+ # empty
+ [ "\\d\n", [ qr{List of relations}, qr{regress_psql_tap_1_t1}, qr{(?!pg_locks)} ] ],
+ [ "\\dS\n", [ qr{List of relations}, qr{pg_locks} ] ],
+ # aggregates
+ [ "\\da\n", [ qr{List of aggregate functions}, qr{(?!Description)} ] ],
+ [ "\\da+\n", [ qr{List of aggregate functions}, qr{(Description)} ] ],
+ [ "\\daS\n", [ qr{array_agg}, qr{avg}, qr{bit_and}, qr{bit_or}, qr{count}, qr{max} ] ],
+ # access methods
+ [ "\\dA\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{(?!Description)} ] ],
+ [ "\\dA+\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{Description} ] ],
+ # tablespaces
+ [ "\\db\n", [ qr{List of tablespaces}, qr{pg_default}, qr{pg_global}, qr{(?!Size)} ] ],
+ [ "\\db+ pg_def*\n", [ qr{List of tablespaces}, qr{pg_default}, qr{(?!pg_global)}, qr{Size} ] ],
+ # functions
+ [ "\\df\n", [ qr{List of functions} ] ],
+ [ "\\df+\n", [ qr{List of functions} ] ],
+ [ "\\dftS\n", [ qr{RI_}, qr{Type}, qr{(?!Volatility)}, qr{(?!Parallel)} ] ],
+ [ "\\dftS+\n", [ qr{Volatility}, qr{Parallel} ] ],
+ [ "\\dfwS\n", [ qr{lag}, qr{dense_rank}, qr{(?!Owner)}, qr{(?!Language)} ] ],
+ [ "\\dfwS+\n", [ qr{cume_dist}, qr{Owner}, qr{Language} ] ],
+ [ "\\dfnS\n", [ qr{abbrev}, qr{(?!bit_or)} ] ],
+ [ "\\dfaS\n", [ qr{bit_and}, qr{(?!abs)} ] ],
+ [ "\\dfpS\n", [ qr{List of functions} ] ],
+ [ "\\dfwtS\n", [ qr{RI_FKey_}, qr{last_value}, qr{nth_value}, qr{(?!bit_and)} ] ],
+ # type
+ [ "\\dTS charac*\n", [ qr{List of data types}, qr{character varying}, qr{(?!double)}, qr{(?!Size)} ] ],
+ [ "\\dTS+\n", [ qr{aclitem}, qr{Size} ] ],
+ # operator
+ [ "\\doS\n", [ qr{List of operators}, qr{!~\*} ] ],
+ [ "\\doS+\n", [ qr{<->}, qr{Result type}] ],
+ # databases
+ [ "\\l\n", [ qr{List of databases}, qr{template0}, qr{template1}, qr{(?!Size)} ] ],
+ [ "\\l a*\n", [ qr{(?!template[01])} ] ],
+ [ "\\l+\n", [ qr{Size}, qr{Encoding} ] ],
+ [ "\\list\n", [ qr{List of databases} ] ],
+ # permissions: well tested
+ # default acls (empty)
+ [ "\\ddp\n", [ qr{Default access privileges} ] ],
+ # descriptions (empty)
+ [ "\\dd\n", [ qr{Object descriptions} ] ],
+ # tables: well tested
+ # roles/users/groups
+ [ "\\du\n", [ qr{Superuser}, qr{regress_psql_tap_1}, qr{List of roles}, qr{(?!Description)} ] ],
+ [ "\\du+\n", [ qr{Superuser}, qr{List of roles}, qr{Description} ] ],
+ [ "\\dg\n", [ qr{Superuser}, qr{List of roles} ] ],
+ # role settings
+ [ "\\drds\n", [ qr{List of settings} ] ],
+ [ "\\drds regress_* *\n", [ qr{List of settings} ] ],
+ # index/...
+ [ "\\diS\n", [ qr{List of relations}, qr{pg_am_oid_index} ] ],
+ [ "\\diS+\n", [ qr{Table} ] ],
+ # partition tables: well tested
+ # large objects
+ [ "\\dl", [ qr{Large objects} ] ],
+ # languages
+ [ "\\dL\n", [ qr{List of languages}, qr{plpgsql}, qr{(?!Trusted)} ] ],
+ [ "\\dL+\n", [ qr{plpgsql}, qr{Trusted} ] ],
+ # domains
+ [ "\\dD\n", [ qr{List of domains}, qr{(?!Access privileges)} ] ],
+ [ "\\dD+\n", [ qr{List of domains}, qr{Access privileges} ] ],
+ # conversions
+ [ "\\dcS\n", [ qr{List of conversions}, qr{UTF8}, qr{(?!Description)} ] ],
+ [ "\\dcS+\n", [ qr{List of conversions}, qr{LATIN1}, qr{Description} ] ],
+ # event triggers (empty)
+ [ "\\dy\n", [ qr{List of event triggers}, qr{(?!Description)} ] ],
+ [ "\\dy+\n", [ qr{List of event triggers}, qr{Description} ] ],
+ # casts
+ [ "\\dC\n", [ qr{List of casts}, qr{timestamp without time zone}, qr{(?!Description)} ] ],
+ [ "\\dC+\n", [ qr{Function}, qr{Implicit}, qr{Description} ] ],
+ # collations
+ [ "\\dOS\n", [ qr{List of collations}, qr{Deterministic}, qr{(?!Description)} ] ],
+ [ "\\dOS+\n", [ qr{POSIX}, qr{Description} ] ],
+ # schemas
+ [ "\\dn\n", [ qr{List of schemas}, qr{Owner}, qr{(?!Description)} ] ],
+ [ "\\dn+\n", [ qr{Access privileges}, qr{Description} ] ],
+ # text search misc.
+ [ "\\dFp\n", [ qr{List of text search parsers}, qr{default} ] ],
+ [ "\\dFp+\n", [ qr{hword}, qr{numword} ] ],
+ [ "\\dFd\n", [ qr{List of text search dictionaries}, qr{simple} ] ],
+ [ "\\dFd+\n", [ qr{Init options}, qr{Description} ] ],
+ [ "\\dFt\n", [ qr{List of text search templates}, qr{simple} ] ],
+ [ "\\dFt+\n", [ qr{Lexize}, qr{Description} ] ],
+ [ "\\dF\n", [ qr{List of text search configurations}, qr{simple} ] ],
+ [ "\\dF+\n", [ qr{email}, qr{url_path} ] ],
+ # extensions
+ [ "\\dx\n", [ qr{List of installed extensions}, qr{Version}, qr{plpgsql} ] ],
+ [ "\\dx+\n", [ qr{Objects in extension}, qr{plpgsql_call_handler} ] ],
+ # other backslash commands
+ [ "\\f\n", [ qr{Field separator is} ] ],
+ [ "\\H\\H\n", [ qr{Output format is html.* is aligned}s ] ],
+ [ "\\\?\n", [ qr{General}, qr{Help}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\C foobar\n", [ qr{Title is "foobar"} ] ],
+ [ "\\cd /\n\\cd\n", [ qr{^$} ] ],
+ [ "\\conninfo\n", [ qr{You are connected to database} ] ],
+ [ "\\copy (SELECT 5432 UNION SELECT 2345 ORDER BY 1) TO STDOUT\n", [ qr/\b2345\b.*\b5432\b/s ] ],
+ [ "\\copyright\n", [ qr/The Regents of the University of California/ ] ],
+ [ "\\encoding\n", [ qr/./ ] ],
+ [ "\\encoding :ENCODING\n", $EMPTY ],
+ [ "\\errverbose\n", [ qr{There is no previous error} ] ],
+ [ "\\lo_list\n", [ qr{Large objects} ] ],
+ [ "\\if true\\q\\endif\n", $EMPTY ],
+ [ "\\set\n", [ qr{ENCODING = }, qr{VERSION_NUM = } ] ],
+ [ "\\set COMP_KEYWORD_CASE preserve-lower\n\\set COMP_KEYWORD_CASE lower\n" .
+ "\\set COMP_KEYWORD_CASE upper\n\\echo :COMP_KEYWORD_CASE", [ qr/upper/ ] ],
+ [ "\\set HISTCONTROL ignorespace\n\\set HISTCONTROL ignoredups\n" .
+ "\\set HISTCONTROL ignoreboth\n\\echo :HISTCONTROL", [ qr/ignoreboth/ ] ],
+ [ "\\set ECHO_HIDDEN on\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/template0/ ] ],
+ [ "\\set ECHO_HIDDEN noexec\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/(?!template0)/ ] ],
+ [ "\\set LAST_ERROR_MESSAGE foo bla\n\\echo :LAST_ERROR_MESSAGE\n", [ qr{foobla} ] ],
+ [ "\\set AUTOCOMMIT off\n\\set ON_ERROR_ROLLBACK on\nSELECT 5432 AS pg;\nSELECT 2345 AS pg;\n",
+ [ qr{5432}, qr{2345} ] ],
+ [ "\\set ON_ERROR_ROLLBACK interactive\n\\echo :ON_ERROR_ROLLBACK\n", [ qr{interactive} ] ],
+ [ "\\set VERBOSITY verbose\n\\echo :VERBOSITY\n", [ qr{verbose} ] ],
+ [ "\\set foo bla\n\\echo :foo\\unset foo\\echo :foo\n", [ qr{^bla.*:foo}s ] ],
+ [ "\\set foo bla\n\\echo :'foo' :\"foo\"\n", [ qr{'bla'}, qr{"bla"} ] ],
+ [ "\\setenv FOO bla\n\\setenv FOO\n", [ qr{^$} ] ],
+ [ "\\timing\nSELECT 1;\n\\timing\n", [ qr{Timing is on.*Timing is off}s, qr(Time: \d+\.\d{3} ms) ] ],
+ [ "\\timing ON\n\\timing OFF\n", [ qr{Timing is on.*Timing is off}s ] ],
+ [ "\\T foo\n", [ qr{Table attributes are "foo"} ] ],
+ [ "\\T\n", [ qr{Table attributes unset} ] ],
+ # help variants
+ [ "\\?\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? commands\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? bad\n", [ qr{General} ] ],
+ [ "\\? variables\n",
+ [ qr{psql variables:}, qr{Display settings:}, qr{Environment variables} ] ],
+ [ "\\? options\n",
+ [ qr{interactive terminal}, qr{Report bugs} ] ],
+ # pset format
+ [ "\\pset format aligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is aligned}, qr{ 1 \| 2} ] ],
+ [ "\\pset format asciidoc\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is asciidoc}, qr{\|1 \|2} ] ],
+ [ "\\pset format csv\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is csv}, qr{1,2} ] ],
+ [ "\\pset format html\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is html}, qr{<td align="right">1</td>} ] ],
+ [ "\\pset format latex\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex}, qr{1 \& 2} ] ],
+ [ "\\pset format latex-longtable\n\\pset format\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex-longtable}, qr{\\raggedright\{1\}} ] ],
+ [ "\\pset format troff-ms\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is troff-ms}, qr{1\t2} ] ],
+ [ "\\pset format unaligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is unaligned}, qr{1\|2} ] ],
+ [ "\\pset format wrapped\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is wrapped}, qr{ 1 \| 2} ] ],
+ [ "\\pset format\n", [ qr{Output format is aligned} ] ],
+ # pset linestyle
+ [ "\\pset linestyle ascii\n", [ qr{Line style is ascii} ] ],
+ [ "\\pset linestyle old-ascii\n", [ qr{Line style is old-ascii} ] ],
+ [ "\\pset linestyle unicode\n", [ qr{Line style is unicode} ] ],
+ # pset unicode_*_linestyle
+ [ "\\pset unicode_header_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_header_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_border_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_border_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_column_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_column_linestyle double\n", [ qr{"double"} ] ],
+);
+
+for my $h (@backslash_out)
+{
+ my ($in, $out) = @$h;
+ psql('', 0, $in, $out, [ qr{^$} ], "psql -- $in");
+}
+
+# test some errors
+my @backslash_err = (
+ [ "\\unknown stuff\n", [ qr/invalid command / ] ],
+ [ "\\cd /no/such/dir\n", [ qr/could not change directory to/ ] ],
+ [ "\\copy\n", [ qr/arguments required/ ] ],
+ [ "\\copy bad stuff on the line\n", [ qr/parse error at \"/ ] ],
+ [ "\\copy binary (\n", [ qr/parse error at end of line/ ] ],
+ [ "\\copy public.regress_psql_tap_1_t1(data) to '/no/such/file'", [ qr{/no/such/file} ] ],
+ [ "\\encoding no-such-encoding\n", [ qr/invalid encoding name/ ] ],
+ [ "BAD;\n\\errverbose\n", [ qr/syntax error at or near "BAD"/, qr/\b42601\b/ ] ],
+ [ "\\dfX\n", [ qr/invalid command/ ] ],
+ [ "\\dPX\n", [ qr/invalid command/ ] ],
+ [ "\\drX\n", [ qr/invalid command/ ] ],
+ [ "\\dRX\n", [ qr/invalid command/ ] ],
+ [ "\\dFX\n", [ qr/invalid command/ ] ],
+ [ "\\deX\n", [ qr/invalid command/ ] ],
+ [ "\\dX\n", [ qr/invalid command/ ] ],
+ [ "\\i\n", [ qr/missing required argument/ ] ],
+ [ "\\ir\n", [ qr/missing required argument/] ],
+ [ "\\lo_bad\n", [ qr/invalid command/ ] ],
+ [ "\\lo_export\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import /no/such/file\n", [ qr{no/such/file} ] ],
+ [ "\\lo_unlink\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_unlink -1\n", [ qr/large object 4294967295 does not exist/ ] ],
+ [ "\\setenv\n", [ qr/missing required argument/ ] ],
+ [ "\\setenv PG_REGRESS_TEST=1\n", [ qr/environment variable name must not contain "="/ ] ],
+ [ "\\unset\n", [ qr/missing required argument/ ] ],
+ [ "\\set foo \`no-such-command\`\n", [ qr/no-such-command/ ] ],
+ [ "\\set === xxx\n", [ qr/invalid variable name/ ] ],
+ [ "\\w\n", [ qr/missing required argument/ ] ],
+ [ "\\pset format bad\n", [ qr/allowed formats are aligned, asciidoc/ ] ],
+ [ "\\pset linestyle bad\n", [ qr/allowed line styles are ascii, old-ascii, unicode/ ] ],
+ [ "\\pset unicode_border_linestyle bad\n", [ qr/allowed Unicode border line styles are single, double/ ] ],
+ [ "\\pset unicode_column_linestyle bad\n", [ qr/allowed Unicode column line styles are single, double/ ] ],
+ [ "\\pset unicode_header_linestyle bad\n", [ qr/allowed Unicode header line styles are single, double/ ] ],
+ [ "COPY regress_psql_tap_1_t1 FROM STDIN \\watch 0.01\n", [ qr/watch cannot be used with COPY/ ] ],
+ [ "\\watch 0.01\n", [ qr/watch cannot be used with an empty query/ ] ],
+ # check variable constraints values
+ [ "\\set ECHO bad\n", [ qr{none, errors, queries, all} ] ],
+ [ "\\set ECHO_HIDDEN bad\n", [ qr{on, off, noexec} ] ],
+ [ "\\set ON_ERROR_ROLLBACK bad\n", [ qr{on, off, interactive} ] ],
+ [ "\\set COMP_KEYWORD_CASE bad\n", [ qr{lower, upper, preserve-lower, preserve-upper} ] ],
+ [ "\\set HISTCONTROL bad\n", [ qr{none, ignorespace, ignoredups, ignoreboth} ] ],
+ [ "\\set VERBOSITY bad\n", [ qr{default, verbose, terse, sqlstate} ] ],
+ [ "\\set SHOW_CONTEXT bad\n", [ qr{never, errors, always} ] ],
+);
+
+for my $h (@backslash_err)
+{
+ my ($in, $err) = @$h;
+ psql('-L /dev/null', 0, $in, [ qr{^$} ], $err, "psql -- $in");
+}
+
+# other errors
+
+# \timing
+psql('', 0, "\\timing bad\n",
+ [ qr{Timing is off} ], [ qr{unrecognized value} ], 'psql \timing error');
+
+# check stdout vs stderr output
+psql('', 0, "\\echo hello\n\\warn world\n\\q\n",
+ [ qr{^hello$} ], [ qr{^world$} ], 'psql in/out/err');
+
+# \watch test
+psql('', 0,
+ "CREATE TEMP SEQUENCE tmp_seq MAXVALUE 3 NO CYCLE;\n" .
+ "\\t on\n" .
+ "\\timing\n" .
+ "SELECT 'x=' || NEXTVAL('tmp_seq') \\watch 0.01\n",
+ [ qr/x=1\b.*x=2\b.*x=3\b/s, qr/(?!x=[04-9])/ ],
+ [ qr/nextval: reached maximum value of sequence/ ],
+ 'psql watch');
+
+# guess whether it looks like unix enough for testing purposes
+my $is_like_unix = -e "/dev/null" and -d "/tmp" and -x "/bin/cat" and -x "/bin/echo";
+
+# skip un*x-specific zone if it does not look like un*x
+goto END_UNIX_ZONE
+ unless $is_like_unix;
+
+# this probably only works under UN*X-like systems
+psql('', 0, "\\setenv PSQL_EDITOR echo\nSELECT 1 AS one;\n\\e\n\\p\n",
+ [ qr/one.*one/s, qr/SELECT 1/ ], $EMPTY, 'psql edit');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ef regress_psql_tap_1_f1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ef');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ev regress_psql_tap_1_v1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ev');
+psql('', 0, "\\setenv FOO bla\\set foo \`echo \$FOO\`\n\\echo :foo\n",
+ [ qr/bla/ ], $EMPTY, 'psql backtick');
+psql('', 0, "\\setenv FOO blabla\n\\! echo \$FOO\n",
+ [ qr/blabla/ ], $EMPTY, 'psql !');
+
+psql('-o /dev/null', 0, "SELECT 5432 AS pg\n", $EMPTY, $EMPTY, 'psql -o null');
+psql('', 0, "\\o /dev/null\nSELECT 1;\n", $EMPTY, $EMPTY, 'psql \o null');
+psql('', 0, "\\o | cat\nSELECT 5432 AS pg;\n\\o\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql \o cat');
+
+psql('', 0, "SELECT 5432 AS pg;\n\\w /dev/null\n",
+ [ qr/pg.*\b5432\b/s ], $EMPTY, 'psql \w null');
+
+psql('', 0, "SELECT 1\\g /dev/null\n",
+ $EMPTY, $EMPTY, 'psql \g null');
+psql('', 0, "SELECT 5432\\g | cat\n",
+ [ qr/\b5432\n/ ], $EMPTY, 'psql \g cat');
+
+psql('--log-file=/dev/null', 0, "SELECT 5432 AS pg\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql -L null');
+
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) FROM PROGRAM 'echo moe'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy echo');
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) TO PROGRAM 'cat'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy cat'); # :-)
+
+END_UNIX_ZONE:
+
+psql('', 0, "\\i /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \i error');
+psql('', 0, "\\i ~/there/is/no/such/file\n",
+ [ qr{^$} ], [ qr{there/is/no/such/file} ], 'psql \i tilde');
+
+# no such file
+psql('', 0, "\\o /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \o error');
+
+psql('', 0, "\\w /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \w error');
+
+psql('-L /no/such/file', 1, '',
+ [ qr{^$} ], [ qr{could not open log file} ], 'psql -L bad');
+
+# \copy errors
+psql('', 0, "\\copy regress_psql_tap_1_t1 to program '/no/such/prgm'",
+ [ qr/COPY 1/ ], [ qr{/no/such/prgm} ], 'psql copy to bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from program '/no/such/prgm'",
+ [ qr/COPY 0/ ], [ qr{/no/such/prgm} ], 'psql copy from bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 to '/tmp'",
+ $EMPTY, [ qr{directory} ], 'psql copy to tmp');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from '/tmp'",
+ $EMPTY, [ qr/directory/ ], 'psql copy to tmp');
+
+# misc errors
+psql('-P format=bad', 1, '', $EMPTY,
+ [ qr/allowed formats are aligned/, qr/could not set printing parameter/ ], 'psql -P bad');
+psql('-Q', 1, '', $EMPTY, [ qr/Try ".* --help" for more information/ ], 'psql -BAD');
+
+#
+# .psqlrc
+#
+# note: reading the system wide psqlrc cannot be avoided
+#
+my $psqlrc = create_test_file('regress_psql_tap_1_psqlrc', "SELECT 1 AS one\n");
+$ENV{PSQLRC} = $psqlrc;
+psql('!', 0, "SELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], [ qr{^$} ], 'psql .psqlrc');
+delete $ENV{PSQLRC};
+ok(unlink $psqlrc, "unlink $psqlrc");
+
+#
+# large objects
+#
+my $lofile = create_test_file('regress_psql_tap_1_lo', '0123456789ABCDEF' x 1024);
+my $esc_lofile = $lofile;
+$esc_lofile =~ s/'/''/g;
+psql('-H', 0,
+ "\\lo_import '$esc_lofile' 'regress psql TAP lo'\n\\lo_list\n",
+ [ qr{lo_import \d+}, qr{Large objects}, qr{regress psql TAP lo} ], [ qr{^$} ], 'psql lo 1');
+ok(unlink $lofile, "unlink $lofile (1)");
+psql('', 0,
+ "SELECT oid AS loboid\n" .
+ "FROM pg_catalog.pg_largeobject_metadata\n" .
+ "WHERE pg_catalog.obj_description(oid, 'pg_largeobject') = 'regress psql TAP lo'\\gset\n" .
+ "\\echo loboid = :loboid\n" .
+ "\\pset tuples_only on\n" .
+ "SELECT 'lo extract: ' || lo_get(:loboid, 826, 3)::TEXT AS extract;\n" .
+ "\\lo_export :loboid '/no/such/dir/stuff'\n" . # this one fails
+ "\\lo_export :loboid '$esc_lofile'\n" .
+ "\\lo_unlink :loboid\n",
+ [ qr{loboid = \d+}, qr{lo extract: \\x414243} ],
+ [ qr{could not open file "/no/such/dir/stuff"} ],
+ 'psql lo 2');
+ok(-e $lofile, "re-created $lofile");
+ok(unlink $lofile, "unlink $lofile (2)");
+
+#
+# INTERACTIVE STUFF
+#
+# tab-completion checks are not very fast.
+# disable pager to work around pagination interactions.
+
+# basic test
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ "\\echo hello world\n", qr/hello world.*postgres=\# /s ],
+ [ "help\n", qr/You are using.*\\copyright.*quit.*postgres=\# /s ],
+ [ "SELECT\nhelp\n\\r\n", qr/help or press control-[CD].*postgres=\# /s ],
+ [ "SELECT\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT (\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT \$\$\nquit\n\$\$\\r\n", qr/Use control-[CD] to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT '\nquit'\\r\n", qr/quit'.*postgres=\# /s ],
+ [ "SELECT '\n\\q\n", qr/Use control-[CD] to quit.*postgres'\# /s ],
+ [ "'\\r\n", qr/buffer reset.*postgres=\# /s ],
+ [ "SELECT 1\n\\p\n\\r\n", qr/SELECT 1.*SELECT 1.*buffer reset.*postgres=\#/s ],
+ #[ "\\prompt tmp_var\nfoobla\n\\echo hello :tmp_var\n\\unset foo\n\\echo world :tmp_var\n",
+ # qr/hello foobla.*world :tmp_var.*postgres=\# /s ],
+ [ "\\password\n", qr/Enter new password: / ],
+ [ "foo\n", qr/Enter it again: / ],
+ [ "bla\n", qr/Passwords didn't match.*postgres=\# /s ],
+ [ "exit\n" ],
+ ],
+ 'ipsql basic', 1);
+
+# prompt
+ipsql('-P pager', 0, 5,
+ [ # default prompts
+ [ "SELECT 1 +\n", qr/postgres-\# / ],
+ [ "(2 * \n", qr/postgres\(\# / ],
+ [ "3) + '\n", qr/postgres\'\# / ],
+ [ "4' + \$\$\n", qr/postgres\$\# /],
+ [ "5\$\$ AS \"\n", qr/postgres\"\# / ],
+ [ "seize\" /*\n", qr/postgres\*\# / ],
+ [ " ignored */;\n", qr/\bseize\b.*\b16\b.*postgres=\# /s ],
+ [ "\\if false\n", qr/postgres\@\# / ],
+ [ "\\echo IGNORED\n", qr/command ignored.*if block.*postgres\@\# /s ],
+ # BUG: prompt with - instead of @
+ [ "SELECT 1;\n", qr/query ignored.*if block.*postgres.\# /s ],
+ [ "\\endif\n", qr/postgres=\# / ],
+ [ "\\set SINGLELINE ON\n", qr/postgres\^\# / ],
+ [ "\\set SINGLELINE OFF\n", qr/postgres=\# / ],
+ [ "COPY regress_psql_tap_1_t1(data) FROM STDIN;\n", qr/>> / ],
+ [ "hello\nworld\n\\.\n", qr/COPY 2\b.*postgres=\# /s ],
+ # set new prompts (not tested: %`)
+ [ "\\set PROMPT1 '%%%[%]%?%M%m%>%n%/%~%#%p%R%x%l%:QUIET:%040'\n", qr/%.*\d+(on|off) /s ],
+ [ "\\set PROMPT1 '1> '\n\\set PROMPT2 '2> '\n\\set PROMPT3 '3> '\n", qr/\b1> / ],
+ [ "COPY regress_psql_tap_1_t1(data)\n", qr/\b2> / ],
+ [ "FROM STDIN;\n", qr/\b3> / ],
+ [ "calvin\nhobbes\nsusie\nmoe\n\\.\n", qr/COPY 4\b.*\b1> /s ],
+ # reset prompts
+ [ "\\set PROMPT1 '%/%R%# '\n", qr/PROMPT1.*postgres=# /s ],
+ [ "\\set PROMPT2 '%/%R%# '\n", qr/PROMPT2.*postgres=# /s ],
+ [ "\\set PROMPT3 '>> '\n", qr/PROMPT3.*postgres=# /s ],
+ ],
+ 'ipsql prompt 1');
+
+goto END_HISTORY_ZONE
+ unless $has_history;
+
+# signals
+my $stop = $is_like_unix ? "\004" : "\003";
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ $stop, qr/\\q/ ],
+ ],
+ 'ipsql signals 1', 1);
+
+psql('', 0, "\\s /dev/null\n", $EMPTY, $EMPTY, 'psql \s null');
+
+# tab-completion tests
+ipsql('-P pager', 0, 5,
+ [ # commands
+ [ "SEL\t", qr/SELECT / ],
+ [ "* FROM pg_catalog.pg_ca\t", qr/pg_catalog\.pg_cast / ],
+ [ "LIMIT 17;\n", qr/\b17 rows.*postgres=\# /s ],
+ # backslash commands
+ [ "\\d\t", qr/(\\da.*\\dy.*postgres=# )?\\d/s ],
+ [ "y\n", qr/List of event triggers.*postgres=\# /s ],
+ # pset
+ [ "\\pset \t", qr/(unicode_column_linestyle.*postgres=\# )?\\pset/s ],
+ [ "tu\t", qr/tuples_only / ],
+ [ "off\n", qr/postgres=\# / ],
+ [ "\\pset unicode_b\t", qr/unicode_border_linestyle / ],
+ [ "\t", qr/(double.*single.*postgres=\# )?/s ],
+ [ "double\n", qr/Unicode border line style is "double".*postgres=\# /s ],
+ # set
+ [ "\\set VERB\t", qr/VERBOSITY / ],
+ [ "\t", qr/(default.*verbose.*postgres=\# )?/s ],
+ [ "d\t\n", qr/default.*postgres=\# /s ],
+ # misc
+ [ "\\cd \t", qr/(.*postgres=\# )?\\cd /s ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # object type completion,
+ # strange: the list is not seen from the test driver
+ # but the coverage works as expected.
+ [ "CREATE \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "DROP \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ALTER \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ANALYZE (\t", qr/(SKIP_LOCKED.*postgres=\# )?ANALYZE \(/s ],
+ [ "V\tOF\t", qr/VERBOSE OFF/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # SET
+ [ "SET \t", qr/(zero_damaged_pages.*postgres=\# )?SET /s ],
+ [ "da\t", qr/datestyle / ],
+ [ "\t", qr/TO / ],
+ [ "\tDE\t", qr/DEFAULT/ ],
+ [ ";\n", qr/SET.*postgres=\# /s ],
+ # psql variables
+ [ "\\ec\t", qr/\\echo / ],
+ [ ":q\t", qr/QUIET/ ],
+ [ ":'PO\t", qr/:'PORT'/ ],
+ [ ":\"HISTC\t", qr/:"HISTCONTROL"/ ],
+ [ "\n", qr/(on|off).*'\d+'.*"[a-z]+".*postgres=\# /s ],
+ # file
+ [ "COPY regress_psql_tap_1_t1 FROM \t", qr/(postgres=\# COPY .* FROM )?/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ ],
+ 'ipsql tab 1');
+
+END_HISTORY_ZONE:
+
+# final cleanup
+psql('', 0, '-- cleanup objects
+DROP VIEW regress_psql_tap_1_v1;
+DROP FUNCTION regress_psql_tap_1_f1;
+DROP TABLE regress_psql_tap_1_t1;
+DROP USER regress_psql_tap_1;
+', [ qr{DROP VIEW}, qr{DROP FUNCTION}, qr{DROP TABLE}, qr{DROP ROLE} ], [ qr{^$} ], 'psql -- drop stuff');
+
+$node->stop();
+done_testing();
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index b685b35282..2692c99e17 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -92,6 +92,7 @@ $node->issues_sql_like(
$node->command_checks_all(
[ 'vacuumdb', '--analyze', '--table', 'vacview', 'postgres' ],
0,
+ '',
[qr/^.*vacuuming database "postgres"/],
[qr/^WARNING.*cannot vacuum non-tables or special system tables/s],
'vacuumdb with view');
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 270bd6c856..9c28bec4a7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1676,6 +1676,28 @@ sub command_checks_all
=pod
+=item $node->icommand_checks(cmd, ...)
+
+TestLib::icommand_checks with our connection parameters.
+See command_ok(...)
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local $ENV{PGHOST} = $self->host;
+ local $ENV{PGPORT} = $self->port;
+
+ TestLib::icommand_checks(@_);
+ return;
+}
+
+=pod
+
=item $node->issues_sql_like(cmd, expected_sql, test_name)
Run a command on the node, then verify that $expected_sql appears in the
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 92199792eb..1b2f7d0cfb 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -53,6 +53,7 @@ use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
+use Expect;
# specify a recent enough version of Test::More to support the
# done_testing() function
@@ -81,6 +82,7 @@ our @EXPORT = qw(
command_like_safe
command_fails_like
command_checks_all
+ icommand_checks
$windows_os
);
@@ -792,6 +794,8 @@ Arguments:
=item C<ret>: Expected exit code
+=item C<in>: Stdin for command
+
=item C<out>: Expected stdout from command
=item C<err>: Expected stderr from command
@@ -806,12 +810,13 @@ sub command_checks_all
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($cmd, $expected_ret, $out, $err, $test_name) = @_;
+ my ($cmd, $expected_ret, $in, $out, $err, $test_name) = @_;
+ $in = '' if not defined $in;
# run command
my ($stdout, $stderr);
print("# Running: " . join(" ", @{$cmd}) . "\n");
- IPC::Run::run($cmd, '>', \$stdout, '2>', \$stderr);
+ IPC::Run::run($cmd, '<', \$in, '>', \$stdout, '2>', \$stderr);
# See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
my $ret = $?;
@@ -840,6 +845,84 @@ sub command_checks_all
=pod
+=item icommand_checks(cmd, ret, timeout, init, inout, name)
+
+Run a command and check its status and outputs.
+Arguments:
+
+=over
+
+=item C<cmd> ref to list for command, options and arguments to run
+
+=item C<ret> expected exit status
+
+=item C<timeout> for interactions
+
+=item C<init> initial output before interacting
+
+=item C<inout> [ [ input, list of output checks ], ... ]
+
+=item C<name> of the test
+
+=back
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($cmd, $expected_ret, $timeout, $init, $inout, $name) = @_;
+
+ my $ps = Expect->spawn(@$cmd);
+ ok(defined $ps, "$name command spawn: @$cmd");
+ return unless defined $ps;
+
+ #$ps->slave->stty(qw(raw -echo));
+ $ps->slave->stty(qw(raw));
+ my $n = 0;
+ for my $test (@$inout)
+ {
+ #warn "test: @$test";
+ my ($in, @out) = @$test;
+ $n++;
+ #warn "in: $in";
+ #warn "out: @out";
+ $ps->send($in);
+ for my $o (@out)
+ {
+ my $ok = $ps->expect($timeout, '-re', $o);
+ # check header on first output
+ if (defined $init)
+ {
+ # beware of race conditions...
+ ok($ps->before =~ $init,
+ "$name header matches /$init/");
+ undef $init;
+ }
+ ok($ok, "$n: $name output matches /$o/");
+ if (not $ok)
+ {
+ print STDERR "last input: ###$in####\n";
+ print STDERR "before: ###" . $ps->before . "###\n" if $ps->before;
+ print STDERR "match: ###" . $ps->match . "###\n" if $ps->match;
+ print STDERR "after: ###" . $ps->after . "###\n" if $ps->after;
+ print STDERR "error: ###" . $ps->error . "###\n" if $ps->error;
+ }
+ }
+ }
+ $ps->soft_close();
+
+ # check status
+ my $ret = $ps->exitstatus;
+ ok($ret == $expected_ret,
+ "$name status (got $ret vs expected $expected_ret)");
+
+ return;
+}
+
+=pod
+
=back
=cut
On Thu, Sep 12, 2019 at 2:15 PM Fabien COELHO <coelho@cri.ensmp.fr> wrote:
Ok. Rebased version added, with some minor changes to improve readability
(comments, variables).Few comments: [...]
Commented line can be removed
Commented lines can be removed
??? can be changed to some suitable heading
tab-complation to be changed to tab-completion
Commented lines can be removed, some more are present below these lines also.Thanks for this review.
The lines were really tests I did that had some issues because of the way
the Expect module works, and are not useful for inclusion in the code
base.Here is a v5.
Few more in icommand_checks subroutine:
+
+ #$ps->slave->stty(qw(raw -echo));
+ $ps->slave->stty(qw(raw));
+ my $n = 0;
+ for my $test (@$inout)
+ {
+ #warn "test: @$test";
+ my ($in, @out) = @$test;
+ $n++;
+ #warn "in: $in";
+ #warn "out: @out";
+ $ps->send($in);
Few unwanted code can be removed.
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Here is a v5.
Few more in icommand_checks subroutine:
Few unwanted code can be removed.
Indeed, more debug and test code.
Attached v6 fixes these, and I checked for remaining scrubs without
finding any.
--
Fabien.
Attachments:
psql-tap-6.patchtext/x-diff; name=psql-tap-6.patchDownload
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index b7d36b65dd..aabc27ab6f 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -506,6 +506,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*checksum verification failed/s],
'pg_basebackup reports checksum mismatch');
@@ -526,6 +527,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt2" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*further.*failures.*will.not.be.reported/s],
'pg_basebackup does not report more than 5 checksum mismatches');
@@ -542,6 +544,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt3" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*7 total checksum verification failures/s],
'pg_basebackup correctly report the total number of checksum mismatches');
diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl
index 59228b916c..cf4811d382 100644
--- a/src/bin/pg_checksums/t/002_actions.pl
+++ b/src/bin/pg_checksums/t/002_actions.pl
@@ -62,6 +62,7 @@ sub check_relation_corruption
'--filenode', $relfilenode_corrupted
],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data for single relfilenode on tablespace $tablespace"
@@ -71,6 +72,7 @@ sub check_relation_corruption
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data on tablespace $tablespace");
@@ -203,6 +205,7 @@ sub fail_corrupt
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/^$/],
[qr/could not read block 0 in file.*$file\":/],
"fails for corrupted data in $file");
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
index 3b63ad230f..ebe9b80a52 100644
--- a/src/bin/pg_controldata/t/001_pg_controldata.pl
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -33,6 +33,7 @@ close $fh;
command_checks_all(
[ 'pg_controldata', $node->data_dir ],
0,
+ '',
[
qr/WARNING: Calculated CRC checksum does not match value stored in file/,
qr/WARNING: invalid WAL segment size/
diff --git a/src/bin/pg_resetwal/t/002_corrupted.pl b/src/bin/pg_resetwal/t/002_corrupted.pl
index f9940d7fc5..1990669d26 100644
--- a/src/bin/pg_resetwal/t/002_corrupted.pl
+++ b/src/bin/pg_resetwal/t/002_corrupted.pl
@@ -30,6 +30,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/
@@ -46,6 +47,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index b82d3f65c4..01010914fe 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -50,7 +50,7 @@ sub pgbench
push @cmd, @args;
- $node->command_checks_all(\@cmd, $stat, $out, $err, $name);
+ $node->command_checks_all(\@cmd, $stat, '', $out, $err, $name);
# cleanup?
#unlink @filenames or die "cannot unlink files (@filenames): $!";
diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl
index f7fa18418b..b58f3548c3 100644
--- a/src/bin/pgbench/t/002_pgbench_no_server.pl
+++ b/src/bin/pgbench/t/002_pgbench_no_server.pl
@@ -25,7 +25,7 @@ sub pgbench
my ($opts, $stat, $out, $err, $name) = @_;
print STDERR "opts=$opts, stat=$stat, out=$out, err=$err, name=$name";
command_checks_all([ 'pgbench', split(/\s+/, $opts) ],
- $stat, $out, $err, $name);
+ $stat, '', $out, $err, $name);
return;
}
@@ -52,7 +52,7 @@ sub pgbench_scripts
push @cmd, '-f', $filename;
}
}
- command_checks_all(\@cmd, $stat, $out, $err, $name);
+ command_checks_all(\@cmd, $stat, '', $out, $err, $name);
return;
}
diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore
index c2862b12d6..d324c1c1fa 100644
--- a/src/bin/psql/.gitignore
+++ b/src/bin/psql/.gitignore
@@ -3,3 +3,4 @@
/sql_help.c
/psql
+/tmp_check
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 69bb297fe7..9473ab01cb 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -60,8 +60,15 @@ uninstall:
clean distclean:
rm -f psql$(X) $(OBJS) lex.backup
+ rm -rf tmp_check
# files removed here are supposed to be in the distribution tarball,
# so do not clean them in the clean/distclean rules
maintainer-clean: distclean
rm -f sql_help.h sql_help.c psqlscanslash.c
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/psql/t/001_psql.pl b/src/bin/psql/t/001_psql.pl
new file mode 100644
index 0000000000..e1d1add824
--- /dev/null
+++ b/src/bin/psql/t/001_psql.pl
@@ -0,0 +1,856 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+my $node = get_new_node('main');
+$node->init();
+$node->start();
+
+# create a file under the test directory
+# return its full path
+sub create_test_file
+{
+ my ($fname, $contents) = @_;
+ my $fn = $node->basedir . '/' . $fname;
+ append_to_file($fn, $contents);
+ return $fn;
+}
+
+# invoke psql
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected exit status
+# - in: input stream
+# - out: list of re to check on stdout
+# - err: list of re to check on stderr
+# - name: of the test
+# - more: more raw arguments
+sub psql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $in, $out, $err, $name, @more) = @_;
+ $opts =~ s/^!// or push @more, '-X';
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+ $node->command_checks_all(\@cmd, $stat, $in, $out, $err, $name);
+ return;
+}
+
+# invoke psql interactively, making it believe it is connected to a tty
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected final exit status
+# - timeout: for interactions, in second
+# - inout: list of input expected re
+# - name: of the test
+# - remove: tell to remove init/start/end checks
+sub ipsql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $timeout, $inout, $name, $remove) = @_;
+
+ # build command to run
+ my @more = ();
+ @more = ('-X') unless $opts =~ /^!/;
+ $opts =~ s/^!//;
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+
+ # check it is running at start and end, and exit cleanly
+ unless ($remove)
+ {
+ unshift @$inout, [ "\\echo STARTING :VERSION_NUM\n", qr/STARTING \d+.*postgres=\# /s ];
+ push @$inout, [ "\\echo ENDING :VERSION_NUM\\q\n", qr/ENDING \d+$/s ];
+ }
+
+ # call the stuff
+ $node->icommand_checks(
+ \@cmd, $stat, $timeout,
+ $remove ? undef : qr/psql .*Type "help" for help..*postgres=# /s,
+ $inout, $name);
+ return;
+}
+
+#
+# check whether we have readline/history capability
+#
+my ($out, $err);
+$node->psql('postgres', "\\s\n", 'stdout' => \$out, 'stderr' => \$err);
+my $has_history = $err !~ /history is not supported by this installation/;
+
+my $EMPTY = [ qr/^$/ ];
+
+#
+# check various options
+#
+# this cannot be tested from within a SQL script
+psql('!--help', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql --help');
+psql('!-?', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql -?');
+psql('!--version', 0, '',
+ [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql --version');
+psql('--help=variables', 0, '',
+ [ qr{VERSION_NAME}, qr{unicode_border_linestyle}, qr{PSQL_EDITOR} ],
+ $EMPTY, 'psql --help=variables');
+# look for plenty of backslash-commands
+psql('--help=commands', 0, '',
+ [ qr{\\copyright}, qr{\\pset}, qr{(?!VERSION_NAME)} ],
+ $EMPTY, 'psql --help=commands');
+
+# tested elsewhere: -a -A -d -e -h -L
+psql('-b', 0, "SELECT 1/0;\n",
+ $EMPTY, [ qr{STATEMENT: SELECT 1/0}, qr{ERROR: division by zero} ], 'psql -b');
+psql('-c \\q', 0, '', $EMPTY, $EMPTY, 'psql -c');
+psql('-c \\l -c \\l', 0, '', [ qr{template0.*template0}s ], $EMPTY, 'psql -c -c');
+psql('', 0, '', [ qr{(?!1234)}, qr{5678}s ], $EMPTY, 'psql -c ;;', ('-c', 'SELECT 1234; SELECT 5678;'));
+psql('-E', 0, "\\l\n", [ qr{\* QUERY \*}, qr{template0} ], $EMPTY, 'psql -E');
+psql('-A -F _', 0, "SELECT 54, 32;\n", [ qr{54_32} ], $EMPTY, 'psql -F _');
+psql('-H', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -H');
+psql('-l', 0, '', [ qr{template0}, qr{template1} ], $EMPTY, 'psql -l');
+psql('-n', 0, "\\q\n", $EMPTY, $EMPTY, 'psql -n');
+psql('-P format=html', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -P format=html');
+psql('-P tuples_only', 0, "\\pset\n", [ qr{tuples_only.*on} ], $EMPTY, 'psql -P tuples_only');
+psql('-A -R_', 0, "VALUES (1), (2);\n", [ qr{_1_2_} ], $EMPTY, 'psql -R _');
+psql('-s', 0, "SELECT 1;\n", [ qr{verify command}, qr{SELECT 1}, qr{press return to proceed} ], $EMPTY, 'psql -s');
+psql('-S', 0, "SELECT 1 AS one\nSELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], $EMPTY, 'psql -S');
+psql('-H -T width=100', 0, "SELECT 1 AS one;\n", [ qr{table.*width=100} ], $EMPTY, 'psql -T ...');
+psql('-X -V', 0, '', [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql -X -V');
+psql('', 0, "\\echo :VERSION_NUM\n", [ qr{\d{6}} ], $EMPTY, 'psql -- show VERSION_NUM');
+psql('-v VERSION_NUM', 0, "\\echo :VERSION_NUM\n", [ qr{:VERSION_NUM} ], $EMPTY, 'psql -v ...');
+psql('-v VERSION_NUM=bla', 0, "\\echo :VERSION_NUM\n", [ qr{bla} ], $EMPTY, 'psql -v ...=.');
+psql('-w', 0, '', $EMPTY, $EMPTY, 'psql -w');
+psql('-x', 0, "SELECT 1 AS one, 2 AS two;\n", [ qr{one \| 1.*two \| 2}s ], $EMPTY, 'psql -x');
+psql('-1', 0, "SELECT 54;\nSELECT 32;\n", [ qr{54}, qr{32} ], $EMPTY, 'psql -1');
+psql('-X -?', 0, '',
+ [ qr{interactive terminal}, qr{Usage:}, qr{Connection options:} ], $EMPTY, 'psql -X -?');
+psql('--csv', 0, "SELECT 1 AS one, 2 AS two\n", [ qr{one,two}, qr{1,2} ], $EMPTY, 'psql --csv');
+
+#
+# create some objects to help \d tests later
+#
+psql('', 0, '-- create some objects
+CREATE USER regress_psql_tap_1;
+ALTER ROLE regress_psql_tap_1 SET enable_indexscan TO off;
+CREATE TABLE regress_psql_tap_1_t1(data TEXT NOT NULL);
+CREATE FUNCTION regress_psql_tap_1_f1() RETURNS INTEGER AS $$ SELECT 54321 $$ LANGUAGE SQL;
+CREATE VIEW regress_psql_tap_1_v1 AS SELECT 54321 AS one;
+', [ qr{CREATE ROLE}, qr{ALTER ROLE} ], [ qr{^$} ], 'psql -- create stuff');
+
+#
+# check interactive help
+#
+# this is mostly for coverage, including occasional negatives.
+# we certainly do not want to have an output file with the whole doc.
+my @backslash_help = (
+ # \h command, list of stdout matches
+ [ 'no-help', [ qr{No help available for} ] ],
+ [ '' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ [ ' ' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ # lists
+ [ 'ALTER', [ qr{AGGREGATE}, qr{ROLE}, qr{VIEW}, qr{(?!ACCESS METHOD)}, qr{(?!TRANSFORM)} ] ],
+ [ 'ALTER TEXT SEARCH', [ qr{CONFIGURATION}, qr{DICTIONARY}, qr{PARSER}, qr{TEMPLATE} ] ],
+ [ 'ALTER FOREIGN', [ qr{DATA WRAPPER}, qr{TABLE} ] ],
+ [ 'CREATE', [ qr{ACCESS METHOD}, qr{TRANSFORM} ] ],
+ [ 'DROP', [ qr{ACCESS METHOD}, qr{VIEW} ] ],
+ # details
+ [ 'ABORT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'ALTER AGGREGATE', [ qr{RENAME TO}, qr{(?!CONVERSION)} ] ],
+ [ 'ALTER CONVERSION', [ qr{SET SCHEMA}, qr{(?!AGGREGATE)} ] ],
+ [ 'ALTER COLLATION', [ qr{REFRESH VERSION} ] ],
+ [ 'ALTER DATABASE', [ qr{CONNECTION LIMIT} ] ],
+ [ 'ALTER DEFAULT PRIVILEGES', [ qr{GRANT} ] ],
+ [ 'ALTER DOMAIN', [ qr{VALIDATE CONSTRAINT} ] ],
+ [ 'ALTER EVENT TRIGGER', [ qr{DISABLE} ] ],
+ [ 'ALTER EXTENSION', [ qr{OPERATOR FAMILY} ] ],
+ [ 'ALTER FOREIGN DATA WRAPPER', [ qr{VALIDATOR} ] ],
+ [ 'ALTER FOREIGN TABLE', [ qr{SET STORAGE} ] ],
+ [ 'ALTER FUNCTION', [ qr{PARALLEL} ] ],
+ [ 'ALTER GROUP', [ qr{DROP USER} ] ],
+ [ 'ALTER INDEX', [ qr{ALL IN TABLESPACE} ] ],
+ [ 'ALTER LANGUAGE', [ qr{PROCEDURAL}, qr{(?!INDEX)} ] ],
+ [ 'ALTER LARGE OBJECT', [ qr{OWNER TO} ] ],
+ [ 'ALTER MATERIALIZED VIEW', [ qr{SET TABLESPACE} ] ],
+ [ 'ALTER OPERATOR', [ qr{JOIN =} ] ],
+ [ 'ALTER OPERATOR CLASS', [ qr{SET SCHEMA}, qr{(?!LANGUAGE)} ] ],
+ [ 'ALTER OPERATOR FAMILY', [ qr{FUNCTION} ] ],
+ [ 'ALTER POLICY', [ qr{WITH CHECK} ] ],
+ [ 'ALTER PROCEDURE', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER PUBLICATION', [ qr{DROP TABLE} ] ],
+ [ 'ALTER ROLE', [ qr{BYPASSRLS} ] ],
+ [ 'ALTER ROUTINE', [ qr{LEAKPROOF} ] ],
+ [ 'ALTER RULE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER SCHEMA', [ qr{OWNER TO} ] ],
+ [ 'ALTER SEQUENCE', [ qr{NO MAXVALUE} ] ],
+ [ 'ALTER SERVER', [ qr{VERSION} ] ],
+ [ 'ALTER STATISTICS', [ qr{SET SCHEMA} ] ],
+ [ 'ALTER SUBSCRIPTION', [ qr{REFRESH PUBLICATION} ] ],
+ [ 'ALTER SYSTEM', [ qr{RESET ALL} ] ],
+ [ 'ALTER TABLE', [ qr{ALL IN TABLESPACE}, qr{DISABLE ROW LEVEL SECURITY} ] ],
+ [ 'ALTER TABLESPACE', [ qr{RESET} ] ],
+ [ 'ALTER TEXT SEARCH CONFIGURATION', [ qr{ALTER MAPPING FOR} ] ],
+ [ 'ALTER TEXT SEARCH DICTIONARY', [ qr{OWNER TO} ] ],
+ [ 'ALTER TEXT SEARCH PARSER', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TEXT SEARCH TEMPLATE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TRIGGER', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER TYPE', [ qr{RENAME ATTRIBUTE} ] ],
+ [ 'ALTER USER', [ qr{NOREPLICATION}, qr{(?!MAPPING)} ] ],
+ [ 'ALTER USER MAPPING', [ qr{SERVER} ] ],
+ [ 'ALTER VIEW', [ qr{DROP DEFAULT} ] ],
+ [ 'ANALYZE', [ qr{VERBOSE}, qr{SKIP_LOCKED} ] ],
+ [ 'BEGIN', [ qr{ISOLATION LEVEL} ] ],
+ [ 'CALL', [] ],
+ [ 'CHECKPOINT', [] ],
+ [ 'CLOSE', [ qr{ALL} ] ],
+ [ 'CLUSTER', [ qr{VERBOSE} ] ],
+ [ 'COMMENT', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'COMMIT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'COMMIT PREPARED', [] ],
+ [ 'COPY', [ qr{FORMAT}, qr{FORCE_NOT_NULL} ] ],
+ [ 'CREATE ACCESS METHOD', [ qr{HANDLER} ] ],
+ [ 'CREATE AGGREGATE', [ qr{SSPACE}, qr{SORTOP}, qr{FINALFUNC} ] ],
+ [ 'CREATE CAST', [ qr{AS IMPLICIT} ] ],
+ [ 'CREATE COLLATION', [ qr{LC_CTYPE}, qr{DETERMINISTIC} ] ],
+ [ 'CREATE CONVERSION', [ qr{FOR} ] ],
+ [ 'CREATE DATABASE', [ qr{ALLOW_CONNECTIONS} ] ],
+ [ 'CREATE DOMAIN', [ qr{COLLATE} ] ],
+ [ 'CREATE EVENT TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE EXTENSION', [ qr{FROM} ] ],
+ [ 'CREATE FOREIGN DATA WRAPPER', [ qr{NO VALIDATOR} ] ],
+ [ 'CREATE FOREIGN TABLE', [ qr{SERVER} ] ],
+ [ 'CREATE FUNCTION', [ qr{CALLED ON NULL INPUT}, qr{(?!PROCEDURE)} ] ],
+ [ 'CREATE GROUP', [ qr{ADMIN} ] ],
+ [ 'CREATE INDEX', [ qr{CONCURRENTLY}, qr{WHERE} ] ],
+ [ 'CREATE LANGUAGE', [ qr{TRUSTED} ] ],
+ [ 'CREATE MATERIALIZED VIEW', [ qr{DATA} ] ],
+ [ 'CREATE OPERATOR', [ qr{LEFTARG} ] ],
+ [ 'CREATE OPERATOR CLASS', [ qr{FOR ORDER BY} ] ],
+ [ 'CREATE OPERATOR FAMILY', [ qr{USING} ] ],
+ [ 'CREATE POLICY', [ qr{PERMISSIVE} ] ],
+ [ 'CREATE PROCEDURE', [ qr{SECURITY INVOKER}, qr{(?!FUNCTION)} ] ],
+ [ 'CREATE PUBLICATION', [ qr{FOR ALL TABLES} ] ],
+ [ 'CREATE ROLE', [ qr{SYSID} ] ],
+ [ 'CREATE RULE', [ qr{ALSO} ] ],
+ [ 'CREATE SCHEMA', [ qr{AUTHORIZATION} ] ],
+ [ 'CREATE SEQUENCE', [ qr{NO MINVALUE} ] ],
+ [ 'CREATE SERVER', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'CREATE STATISTICS', [ qr{FROM} ] ],
+ [ 'CREATE SUBSCRIPTION', [ qr{CONNECTION} ] ],
+ [ 'CREATE TABLE', [ qr{PARTITION BY} ] ],
+ [ 'CREATE TABLE AS', [ qr{ON COMMIT} ] ],
+ [ 'CREATE TABLESPACE', [ qr{LOCATION} ] ],
+ [ 'CREATE TEXT SEARCH CONFIGURATION', [ qr{PARSER} ] ],
+ [ 'CREATE TEXT SEARCH DICTIONARY', [ qr{TEMPLATE} ] ],
+ [ 'CREATE TEXT SEARCH PARSER', [ qr{GETTOKEN} ] ],
+ [ 'CREATE TEXT SEARCH TEMPLATE', [ qr{LEXIZE} ] ],
+ [ 'CREATE TRANSFORM', [ qr{FROM SQL WITH FUNCTION} ] ],
+ [ 'CREATE TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE TYPE', [ qr{SUBTYPE} ] ],
+ [ 'CREATE USER', [ qr{PASSWORD NULL} ] ],
+ [ 'CREATE USER MAPPING', [ qr{SERVER} ] ],
+ [ 'CREATE VIEW', [ qr{CASCADED} ] ],
+ [ 'DEALLOCATE', [ qr{PREPARE} ] ],
+ [ 'DECLARE', [ qr{INSENSITIVE} ] ],
+ [ 'DELETE', [ qr{WHERE CURRENT OF} ] ],
+ [ 'DISCARD', [ qr{PLANS} ] ],
+ [ 'DO', [ qr{LANGUAGE} ] ],
+ [ 'DROP ACCESS METHOD', [ qr{CASCADE} ] ],
+ [ 'DROP AGGREGATE', [ qr{ORDER BY} ] ],
+ [ 'DROP CAST', [ qr{RESTRICT} ] ],
+ [ 'DROP COLLATION', [ qr{RESTRICT} ] ],
+ [ 'DROP CONVERSION', [ qr{RESTRICT} ] ],
+ [ 'DROP DATABASE', [ qr{IF EXISTS} ] ],
+ [ 'DROP DOMAIN', [ qr{RESTRICT} ] ],
+ [ 'DROP EVENT TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP EXTENSION', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN DATA WRAPPER', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP FUNCTION', [ qr{RESTRICT} ] ],
+ [ 'DROP GROUP', [ qr{IF EXISTS} ] ],
+ [ 'DROP INDEX', [ qr{RESTRICT} ] ],
+ [ 'DROP LANGUAGE', [ qr{RESTRICT} ] ],
+ [ 'DROP MATERIALIZED VIEW', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR CLASS', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR FAMILY', [ qr{RESTRICT} ] ],
+ [ 'DROP OWNED', [ qr{CURRENT_USER} ] ],
+ [ 'DROP POLICY', [ qr{RESTRICT} ] ],
+ [ 'DROP PROCEDURE', [ qr{RESTRICT} ] ],
+ [ 'DROP PUBLICATION', [ qr{RESTRICT} ] ],
+ [ 'DROP ROLE', [ qr{IF EXISTS} ] ],
+ [ 'DROP ROUTINE', [ qr{RESTRICT} ] ],
+ [ 'DROP RULE', [ qr{RESTRICT} ] ],
+ [ 'DROP SCHEMA', [ qr{RESTRICT} ] ],
+ [ 'DROP SEQUENCE', [ qr{RESTRICT} ] ],
+ [ 'DROP SERVER', [ qr{RESTRICT} ] ],
+ [ 'DROP STATISTICS', [ qr{IF EXISTS} ] ],
+ [ 'DROP SUBSCRIPTION', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLESPACE', [ qr{IF EXISTS} ] ],
+ [ 'DROP TEXT SEARCH CONFIGURATION', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH DICTIONARY', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH PARSER', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH TEMPLATE', [ qr{RESTRICT} ] ],
+ [ 'DROP TRANSFORM', [ qr{LANGUAGE} ] ],
+ [ 'DROP TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP TYPE', [ qr{RESTRICT} ] ],
+ [ 'DROP USER', [ qr{IF EXISTS} ] ],
+ [ 'DROP USER MAPPING', [ qr{SERVER} ] ],
+ [ 'DROP VIEW', [ qr{RESTRICT} ] ],
+ [ 'END', [ qr{TRANSACTION}, qr{CHAIN}, qr{(?!COMMIT)} ] ],
+ [ 'EXECUTE', [ ] ],
+ [ 'EXPLAIN', [ qr{VERBOSE} ] ],
+ [ 'FETCH', [ qr{RELATIVE} ] ],
+ [ 'GRANT', [ qr{ALL TABLES} ] ],
+ [ 'IMPORT FOREIGN SCHEMA', [ qr{FROM SERVER} ] ],
+ [ 'INSERT', [ qr{ON CONFLICT} ] ],
+ [ 'LISTEN', [ ] ],
+ [ 'LOAD', [ ] ],
+ [ 'LOCK', [ qr{NOWAIT} ] ],
+ [ 'MOVE', [ qr{FORWARD} ] ],
+ [ 'NOTIFY', [ ] ],
+ [ 'PREPARE', [ qr{AS} ] ],
+ [ 'PREPARE TRANSACTION', [ ] ],
+ [ 'REASSIGN OWNED', [ qr{SESSION_USER} ] ],
+ [ 'REFRESH MATERIALIZED VIEW', [ qr{CONCURRENTLY} ] ],
+ [ 'REINDEX', [ qr{DATABASE} ] ],
+ [ 'RELEASE', [ qr{SAVEPOINT} ] ],
+ [ 'RESET', [ qr{RESET ALL} ] ],
+ [ 'REVOKE', [ qr{PRIVILEGES} ] ],
+ [ 'ROLLBACK', [ qr{CHAIN} ] ],
+ [ 'ROLLBACK PREPARED', [ ]],
+ [ 'SAVEPOINT', [ ] ],
+ [ 'SECURITY LABEL', [ qr{SUBSCRIPTION} ] ],
+ [ 'SELECT', [ qr{RECURSIVE} ] ],
+ [ 'SET', [ qr{SESSION} ] ],
+ [ 'SET CONSTRAINTS', [ qr{IMMEDIATE} ] ],
+ [ 'SET ROLE', [ qr{ROLE NONE} ] ],
+ [ 'SET SESSION AUTHORIZATION', [ qr{DEFAULT} ] ],
+ [ 'SET TRANSACTION', [ qr{SNAPSHOT} ] ],
+ [ 'SHOW', [ qr{SHOW ALL} ] ],
+ [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ],
+ [ 'TABLE', [ qr{ONLY} ] ], # hmmm...
+ [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ],
+ [ 'UNLISTEN', [ ] ],
+ [ 'UPDATE', [ qr{RETURNING} ] ],
+ [ 'VACUUM', [ qr{FREEZE} ] ],
+ [ 'VALUES', [ qr{ORDER BY} ] ],
+ [ 'WITH', [ qr{RECURSIVE} ] ], # SELECT duplicate?
+);
+
+for my $h (@backslash_help)
+{
+ my ($cmd, $out) = @$h;
+ push @$out, qr{$cmd};
+ psql('', 0, "\\h $cmd\n", $out, [ qr{^$} ], "psql -- \\h $cmd");
+}
+
+# special cases
+psql('', 0, "\\h ROLLBACK TO SAVEPOINT\n", [ qr{ROLLBACK}, qr{SAVEPOINT} ],
+ [ qr{^$} ], "psql -- \\h ROLLBACK TO SAVEPOINT");
+psql('', 0, "\\h SELECT INTO\n", [ qr{SELECT}, qr{INTO} ],
+ [ qr{^$} ], "psql -- \\h SELECT INTO");
+
+#
+# check describe and other backslash commands
+#
+# the output can vary significantly, especially with +
+my @backslash_out = (
+ # empty
+ [ "\\d\n", [ qr{List of relations}, qr{regress_psql_tap_1_t1}, qr{(?!pg_locks)} ] ],
+ [ "\\dS\n", [ qr{List of relations}, qr{pg_locks} ] ],
+ # aggregates
+ [ "\\da\n", [ qr{List of aggregate functions}, qr{(?!Description)} ] ],
+ [ "\\da+\n", [ qr{List of aggregate functions}, qr{(Description)} ] ],
+ [ "\\daS\n", [ qr{array_agg}, qr{avg}, qr{bit_and}, qr{bit_or}, qr{count}, qr{max} ] ],
+ # access methods
+ [ "\\dA\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{(?!Description)} ] ],
+ [ "\\dA+\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{Description} ] ],
+ # tablespaces
+ [ "\\db\n", [ qr{List of tablespaces}, qr{pg_default}, qr{pg_global}, qr{(?!Size)} ] ],
+ [ "\\db+ pg_def*\n", [ qr{List of tablespaces}, qr{pg_default}, qr{(?!pg_global)}, qr{Size} ] ],
+ # functions
+ [ "\\df\n", [ qr{List of functions} ] ],
+ [ "\\df+\n", [ qr{List of functions} ] ],
+ [ "\\dftS\n", [ qr{RI_}, qr{Type}, qr{(?!Volatility)}, qr{(?!Parallel)} ] ],
+ [ "\\dftS+\n", [ qr{Volatility}, qr{Parallel} ] ],
+ [ "\\dfwS\n", [ qr{lag}, qr{dense_rank}, qr{(?!Owner)}, qr{(?!Language)} ] ],
+ [ "\\dfwS+\n", [ qr{cume_dist}, qr{Owner}, qr{Language} ] ],
+ [ "\\dfnS\n", [ qr{abbrev}, qr{(?!bit_or)} ] ],
+ [ "\\dfaS\n", [ qr{bit_and}, qr{(?!abs)} ] ],
+ [ "\\dfpS\n", [ qr{List of functions} ] ],
+ [ "\\dfwtS\n", [ qr{RI_FKey_}, qr{last_value}, qr{nth_value}, qr{(?!bit_and)} ] ],
+ # type
+ [ "\\dTS charac*\n", [ qr{List of data types}, qr{character varying}, qr{(?!double)}, qr{(?!Size)} ] ],
+ [ "\\dTS+\n", [ qr{aclitem}, qr{Size} ] ],
+ # operator
+ [ "\\doS\n", [ qr{List of operators}, qr{!~\*} ] ],
+ [ "\\doS+\n", [ qr{<->}, qr{Result type}] ],
+ # databases
+ [ "\\l\n", [ qr{List of databases}, qr{template0}, qr{template1}, qr{(?!Size)} ] ],
+ [ "\\l a*\n", [ qr{(?!template[01])} ] ],
+ [ "\\l+\n", [ qr{Size}, qr{Encoding} ] ],
+ [ "\\list\n", [ qr{List of databases} ] ],
+ # permissions: well tested
+ # default acls (empty)
+ [ "\\ddp\n", [ qr{Default access privileges} ] ],
+ # descriptions (empty)
+ [ "\\dd\n", [ qr{Object descriptions} ] ],
+ # tables: well tested
+ # roles/users/groups
+ [ "\\du\n", [ qr{Superuser}, qr{regress_psql_tap_1}, qr{List of roles}, qr{(?!Description)} ] ],
+ [ "\\du+\n", [ qr{Superuser}, qr{List of roles}, qr{Description} ] ],
+ [ "\\dg\n", [ qr{Superuser}, qr{List of roles} ] ],
+ # role settings
+ [ "\\drds\n", [ qr{List of settings} ] ],
+ [ "\\drds regress_* *\n", [ qr{List of settings} ] ],
+ # index/...
+ [ "\\diS\n", [ qr{List of relations}, qr{pg_am_oid_index} ] ],
+ [ "\\diS+\n", [ qr{Table} ] ],
+ # partition tables: well tested
+ # large objects
+ [ "\\dl", [ qr{Large objects} ] ],
+ # languages
+ [ "\\dL\n", [ qr{List of languages}, qr{plpgsql}, qr{(?!Trusted)} ] ],
+ [ "\\dL+\n", [ qr{plpgsql}, qr{Trusted} ] ],
+ # domains
+ [ "\\dD\n", [ qr{List of domains}, qr{(?!Access privileges)} ] ],
+ [ "\\dD+\n", [ qr{List of domains}, qr{Access privileges} ] ],
+ # conversions
+ [ "\\dcS\n", [ qr{List of conversions}, qr{UTF8}, qr{(?!Description)} ] ],
+ [ "\\dcS+\n", [ qr{List of conversions}, qr{LATIN1}, qr{Description} ] ],
+ # event triggers (empty)
+ [ "\\dy\n", [ qr{List of event triggers}, qr{(?!Description)} ] ],
+ [ "\\dy+\n", [ qr{List of event triggers}, qr{Description} ] ],
+ # casts
+ [ "\\dC\n", [ qr{List of casts}, qr{timestamp without time zone}, qr{(?!Description)} ] ],
+ [ "\\dC+\n", [ qr{Function}, qr{Implicit}, qr{Description} ] ],
+ # collations
+ [ "\\dOS\n", [ qr{List of collations}, qr{Deterministic}, qr{(?!Description)} ] ],
+ [ "\\dOS+\n", [ qr{POSIX}, qr{Description} ] ],
+ # schemas
+ [ "\\dn\n", [ qr{List of schemas}, qr{Owner}, qr{(?!Description)} ] ],
+ [ "\\dn+\n", [ qr{Access privileges}, qr{Description} ] ],
+ # text search misc.
+ [ "\\dFp\n", [ qr{List of text search parsers}, qr{default} ] ],
+ [ "\\dFp+\n", [ qr{hword}, qr{numword} ] ],
+ [ "\\dFd\n", [ qr{List of text search dictionaries}, qr{simple} ] ],
+ [ "\\dFd+\n", [ qr{Init options}, qr{Description} ] ],
+ [ "\\dFt\n", [ qr{List of text search templates}, qr{simple} ] ],
+ [ "\\dFt+\n", [ qr{Lexize}, qr{Description} ] ],
+ [ "\\dF\n", [ qr{List of text search configurations}, qr{simple} ] ],
+ [ "\\dF+\n", [ qr{email}, qr{url_path} ] ],
+ # extensions
+ [ "\\dx\n", [ qr{List of installed extensions}, qr{Version}, qr{plpgsql} ] ],
+ [ "\\dx+\n", [ qr{Objects in extension}, qr{plpgsql_call_handler} ] ],
+ # other backslash commands
+ [ "\\f\n", [ qr{Field separator is} ] ],
+ [ "\\H\\H\n", [ qr{Output format is html.* is aligned}s ] ],
+ [ "\\\?\n", [ qr{General}, qr{Help}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\C foobar\n", [ qr{Title is "foobar"} ] ],
+ [ "\\cd /\n\\cd\n", [ qr{^$} ] ],
+ [ "\\conninfo\n", [ qr{You are connected to database} ] ],
+ [ "\\copy (SELECT 5432 UNION SELECT 2345 ORDER BY 1) TO STDOUT\n", [ qr/\b2345\b.*\b5432\b/s ] ],
+ [ "\\copyright\n", [ qr/The Regents of the University of California/ ] ],
+ [ "\\encoding\n", [ qr/./ ] ],
+ [ "\\encoding :ENCODING\n", $EMPTY ],
+ [ "\\errverbose\n", [ qr{There is no previous error} ] ],
+ [ "\\lo_list\n", [ qr{Large objects} ] ],
+ [ "\\if true\\q\\endif\n", $EMPTY ],
+ [ "\\set\n", [ qr{ENCODING = }, qr{VERSION_NUM = } ] ],
+ [ "\\set COMP_KEYWORD_CASE preserve-lower\n\\set COMP_KEYWORD_CASE lower\n" .
+ "\\set COMP_KEYWORD_CASE upper\n\\echo :COMP_KEYWORD_CASE", [ qr/upper/ ] ],
+ [ "\\set HISTCONTROL ignorespace\n\\set HISTCONTROL ignoredups\n" .
+ "\\set HISTCONTROL ignoreboth\n\\echo :HISTCONTROL", [ qr/ignoreboth/ ] ],
+ [ "\\set ECHO_HIDDEN on\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/template0/ ] ],
+ [ "\\set ECHO_HIDDEN noexec\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/(?!template0)/ ] ],
+ [ "\\set LAST_ERROR_MESSAGE foo bla\n\\echo :LAST_ERROR_MESSAGE\n", [ qr{foobla} ] ],
+ [ "\\set AUTOCOMMIT off\n\\set ON_ERROR_ROLLBACK on\nSELECT 5432 AS pg;\nSELECT 2345 AS pg;\n",
+ [ qr{5432}, qr{2345} ] ],
+ [ "\\set ON_ERROR_ROLLBACK interactive\n\\echo :ON_ERROR_ROLLBACK\n", [ qr{interactive} ] ],
+ [ "\\set VERBOSITY verbose\n\\echo :VERBOSITY\n", [ qr{verbose} ] ],
+ [ "\\set foo bla\n\\echo :foo\\unset foo\\echo :foo\n", [ qr{^bla.*:foo}s ] ],
+ [ "\\set foo bla\n\\echo :'foo' :\"foo\"\n", [ qr{'bla'}, qr{"bla"} ] ],
+ [ "\\setenv FOO bla\n\\setenv FOO\n", [ qr{^$} ] ],
+ [ "\\timing\nSELECT 1;\n\\timing\n", [ qr{Timing is on.*Timing is off}s, qr(Time: \d+\.\d{3} ms) ] ],
+ [ "\\timing ON\n\\timing OFF\n", [ qr{Timing is on.*Timing is off}s ] ],
+ [ "\\T foo\n", [ qr{Table attributes are "foo"} ] ],
+ [ "\\T\n", [ qr{Table attributes unset} ] ],
+ # help variants
+ [ "\\?\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? commands\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? bad\n", [ qr{General} ] ],
+ [ "\\? variables\n",
+ [ qr{psql variables:}, qr{Display settings:}, qr{Environment variables} ] ],
+ [ "\\? options\n",
+ [ qr{interactive terminal}, qr{Report bugs} ] ],
+ # pset format
+ [ "\\pset format aligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is aligned}, qr{ 1 \| 2} ] ],
+ [ "\\pset format asciidoc\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is asciidoc}, qr{\|1 \|2} ] ],
+ [ "\\pset format csv\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is csv}, qr{1,2} ] ],
+ [ "\\pset format html\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is html}, qr{<td align="right">1</td>} ] ],
+ [ "\\pset format latex\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex}, qr{1 \& 2} ] ],
+ [ "\\pset format latex-longtable\n\\pset format\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex-longtable}, qr{\\raggedright\{1\}} ] ],
+ [ "\\pset format troff-ms\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is troff-ms}, qr{1\t2} ] ],
+ [ "\\pset format unaligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is unaligned}, qr{1\|2} ] ],
+ [ "\\pset format wrapped\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is wrapped}, qr{ 1 \| 2} ] ],
+ [ "\\pset format\n", [ qr{Output format is aligned} ] ],
+ # pset linestyle
+ [ "\\pset linestyle ascii\n", [ qr{Line style is ascii} ] ],
+ [ "\\pset linestyle old-ascii\n", [ qr{Line style is old-ascii} ] ],
+ [ "\\pset linestyle unicode\n", [ qr{Line style is unicode} ] ],
+ # pset unicode_*_linestyle
+ [ "\\pset unicode_header_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_header_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_border_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_border_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_column_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_column_linestyle double\n", [ qr{"double"} ] ],
+);
+
+for my $h (@backslash_out)
+{
+ my ($in, $out) = @$h;
+ psql('', 0, $in, $out, [ qr{^$} ], "psql -- $in");
+}
+
+# test some errors
+my @backslash_err = (
+ [ "\\unknown stuff\n", [ qr/invalid command / ] ],
+ [ "\\cd /no/such/dir\n", [ qr/could not change directory to/ ] ],
+ [ "\\copy\n", [ qr/arguments required/ ] ],
+ [ "\\copy bad stuff on the line\n", [ qr/parse error at \"/ ] ],
+ [ "\\copy binary (\n", [ qr/parse error at end of line/ ] ],
+ [ "\\copy public.regress_psql_tap_1_t1(data) to '/no/such/file'", [ qr{/no/such/file} ] ],
+ [ "\\encoding no-such-encoding\n", [ qr/invalid encoding name/ ] ],
+ [ "BAD;\n\\errverbose\n", [ qr/syntax error at or near "BAD"/, qr/\b42601\b/ ] ],
+ [ "\\dfX\n", [ qr/invalid command/ ] ],
+ [ "\\dPX\n", [ qr/invalid command/ ] ],
+ [ "\\drX\n", [ qr/invalid command/ ] ],
+ [ "\\dRX\n", [ qr/invalid command/ ] ],
+ [ "\\dFX\n", [ qr/invalid command/ ] ],
+ [ "\\deX\n", [ qr/invalid command/ ] ],
+ [ "\\dX\n", [ qr/invalid command/ ] ],
+ [ "\\i\n", [ qr/missing required argument/ ] ],
+ [ "\\ir\n", [ qr/missing required argument/] ],
+ [ "\\lo_bad\n", [ qr/invalid command/ ] ],
+ [ "\\lo_export\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import /no/such/file\n", [ qr{no/such/file} ] ],
+ [ "\\lo_unlink\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_unlink -1\n", [ qr/large object 4294967295 does not exist/ ] ],
+ [ "\\setenv\n", [ qr/missing required argument/ ] ],
+ [ "\\setenv PG_REGRESS_TEST=1\n", [ qr/environment variable name must not contain "="/ ] ],
+ [ "\\unset\n", [ qr/missing required argument/ ] ],
+ [ "\\set foo \`no-such-command\`\n", [ qr/no-such-command/ ] ],
+ [ "\\set === xxx\n", [ qr/invalid variable name/ ] ],
+ [ "\\w\n", [ qr/missing required argument/ ] ],
+ [ "\\pset format bad\n", [ qr/allowed formats are aligned, asciidoc/ ] ],
+ [ "\\pset linestyle bad\n", [ qr/allowed line styles are ascii, old-ascii, unicode/ ] ],
+ [ "\\pset unicode_border_linestyle bad\n", [ qr/allowed Unicode border line styles are single, double/ ] ],
+ [ "\\pset unicode_column_linestyle bad\n", [ qr/allowed Unicode column line styles are single, double/ ] ],
+ [ "\\pset unicode_header_linestyle bad\n", [ qr/allowed Unicode header line styles are single, double/ ] ],
+ [ "COPY regress_psql_tap_1_t1 FROM STDIN \\watch 0.01\n", [ qr/watch cannot be used with COPY/ ] ],
+ [ "\\watch 0.01\n", [ qr/watch cannot be used with an empty query/ ] ],
+ # check variable constraints values
+ [ "\\set ECHO bad\n", [ qr{none, errors, queries, all} ] ],
+ [ "\\set ECHO_HIDDEN bad\n", [ qr{on, off, noexec} ] ],
+ [ "\\set ON_ERROR_ROLLBACK bad\n", [ qr{on, off, interactive} ] ],
+ [ "\\set COMP_KEYWORD_CASE bad\n", [ qr{lower, upper, preserve-lower, preserve-upper} ] ],
+ [ "\\set HISTCONTROL bad\n", [ qr{none, ignorespace, ignoredups, ignoreboth} ] ],
+ [ "\\set VERBOSITY bad\n", [ qr{default, verbose, terse, sqlstate} ] ],
+ [ "\\set SHOW_CONTEXT bad\n", [ qr{never, errors, always} ] ],
+);
+
+for my $h (@backslash_err)
+{
+ my ($in, $err) = @$h;
+ psql('-L /dev/null', 0, $in, [ qr{^$} ], $err, "psql -- $in");
+}
+
+# other errors
+
+# \timing
+psql('', 0, "\\timing bad\n",
+ [ qr{Timing is off} ], [ qr{unrecognized value} ], 'psql \timing error');
+
+# check stdout vs stderr output
+psql('', 0, "\\echo hello\n\\warn world\n\\q\n",
+ [ qr{^hello$} ], [ qr{^world$} ], 'psql in/out/err');
+
+# \watch test
+psql('', 0,
+ "CREATE TEMP SEQUENCE tmp_seq MAXVALUE 3 NO CYCLE;\n" .
+ "\\t on\n" .
+ "\\timing\n" .
+ "SELECT 'x=' || NEXTVAL('tmp_seq') \\watch 0.01\n",
+ [ qr/x=1\b.*x=2\b.*x=3\b/s, qr/(?!x=[04-9])/ ],
+ [ qr/nextval: reached maximum value of sequence/ ],
+ 'psql watch');
+
+# guess whether it looks like unix enough for testing purposes
+my $is_like_unix = -e "/dev/null" and -d "/tmp" and -x "/bin/cat" and -x "/bin/echo";
+
+# skip un*x-specific zone if it does not look like un*x
+goto END_UNIX_ZONE
+ unless $is_like_unix;
+
+# this probably only works under UN*X-like systems
+psql('', 0, "\\setenv PSQL_EDITOR echo\nSELECT 1 AS one;\n\\e\n\\p\n",
+ [ qr/one.*one/s, qr/SELECT 1/ ], $EMPTY, 'psql edit');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ef regress_psql_tap_1_f1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ef');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ev regress_psql_tap_1_v1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ev');
+psql('', 0, "\\setenv FOO bla\\set foo \`echo \$FOO\`\n\\echo :foo\n",
+ [ qr/bla/ ], $EMPTY, 'psql backtick');
+psql('', 0, "\\setenv FOO blabla\n\\! echo \$FOO\n",
+ [ qr/blabla/ ], $EMPTY, 'psql !');
+
+psql('-o /dev/null', 0, "SELECT 5432 AS pg\n", $EMPTY, $EMPTY, 'psql -o null');
+psql('', 0, "\\o /dev/null\nSELECT 1;\n", $EMPTY, $EMPTY, 'psql \o null');
+psql('', 0, "\\o | cat\nSELECT 5432 AS pg;\n\\o\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql \o cat');
+
+psql('', 0, "SELECT 5432 AS pg;\n\\w /dev/null\n",
+ [ qr/pg.*\b5432\b/s ], $EMPTY, 'psql \w null');
+
+psql('', 0, "SELECT 1\\g /dev/null\n",
+ $EMPTY, $EMPTY, 'psql \g null');
+psql('', 0, "SELECT 5432\\g | cat\n",
+ [ qr/\b5432\n/ ], $EMPTY, 'psql \g cat');
+
+psql('--log-file=/dev/null', 0, "SELECT 5432 AS pg\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql -L null');
+
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) FROM PROGRAM 'echo moe'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy echo');
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) TO PROGRAM 'cat'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy cat'); # :-)
+
+END_UNIX_ZONE:
+
+psql('', 0, "\\i /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \i error');
+psql('', 0, "\\i ~/there/is/no/such/file\n",
+ [ qr{^$} ], [ qr{there/is/no/such/file} ], 'psql \i tilde');
+
+# no such file
+psql('', 0, "\\o /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \o error');
+
+psql('', 0, "\\w /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \w error');
+
+psql('-L /no/such/file', 1, '',
+ [ qr{^$} ], [ qr{could not open log file} ], 'psql -L bad');
+
+# \copy errors
+psql('', 0, "\\copy regress_psql_tap_1_t1 to program '/no/such/prgm'",
+ [ qr/COPY 1/ ], [ qr{/no/such/prgm} ], 'psql copy to bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from program '/no/such/prgm'",
+ [ qr/COPY 0/ ], [ qr{/no/such/prgm} ], 'psql copy from bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 to '/tmp'",
+ $EMPTY, [ qr{directory} ], 'psql copy to tmp');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from '/tmp'",
+ $EMPTY, [ qr/directory/ ], 'psql copy to tmp');
+
+# misc errors
+psql('-P format=bad', 1, '', $EMPTY,
+ [ qr/allowed formats are aligned/, qr/could not set printing parameter/ ], 'psql -P bad');
+psql('-Q', 1, '', $EMPTY, [ qr/Try ".* --help" for more information/ ], 'psql -BAD');
+
+#
+# .psqlrc
+#
+# note: reading the system wide psqlrc cannot be avoided
+#
+my $psqlrc = create_test_file('regress_psql_tap_1_psqlrc', "SELECT 1 AS one\n");
+$ENV{PSQLRC} = $psqlrc;
+psql('!', 0, "SELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], [ qr{^$} ], 'psql .psqlrc');
+delete $ENV{PSQLRC};
+ok(unlink $psqlrc, "unlink $psqlrc");
+
+#
+# large objects
+#
+my $lofile = create_test_file('regress_psql_tap_1_lo', '0123456789ABCDEF' x 1024);
+my $esc_lofile = $lofile;
+$esc_lofile =~ s/'/''/g;
+psql('-H', 0,
+ "\\lo_import '$esc_lofile' 'regress psql TAP lo'\n\\lo_list\n",
+ [ qr{lo_import \d+}, qr{Large objects}, qr{regress psql TAP lo} ], [ qr{^$} ], 'psql lo 1');
+ok(unlink $lofile, "unlink $lofile (1)");
+psql('', 0,
+ "SELECT oid AS loboid\n" .
+ "FROM pg_catalog.pg_largeobject_metadata\n" .
+ "WHERE pg_catalog.obj_description(oid, 'pg_largeobject') = 'regress psql TAP lo'\\gset\n" .
+ "\\echo loboid = :loboid\n" .
+ "\\pset tuples_only on\n" .
+ "SELECT 'lo extract: ' || lo_get(:loboid, 826, 3)::TEXT AS extract;\n" .
+ "\\lo_export :loboid '/no/such/dir/stuff'\n" . # this one fails
+ "\\lo_export :loboid '$esc_lofile'\n" .
+ "\\lo_unlink :loboid\n",
+ [ qr{loboid = \d+}, qr{lo extract: \\x414243} ],
+ [ qr{could not open file "/no/such/dir/stuff"} ],
+ 'psql lo 2');
+ok(-e $lofile, "re-created $lofile");
+ok(unlink $lofile, "unlink $lofile (2)");
+
+#
+# INTERACTIVE STUFF
+#
+# tab-completion checks are not very fast.
+# disable pager to work around pagination interactions.
+
+# basic test
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ "\\echo hello world\n", qr/hello world.*postgres=\# /s ],
+ [ "help\n", qr/You are using.*\\copyright.*quit.*postgres=\# /s ],
+ [ "SELECT\nhelp\n\\r\n", qr/help or press control-[CD].*postgres=\# /s ],
+ [ "SELECT\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT (\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT \$\$\nquit\n\$\$\\r\n", qr/Use control-[CD] to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT '\nquit'\\r\n", qr/quit'.*postgres=\# /s ],
+ [ "SELECT '\n\\q\n", qr/Use control-[CD] to quit.*postgres'\# /s ],
+ [ "'\\r\n", qr/buffer reset.*postgres=\# /s ],
+ [ "SELECT 1\n\\p\n\\r\n", qr/SELECT 1.*SELECT 1.*buffer reset.*postgres=\#/s ],
+ #[ "\\prompt tmp_var\nfoobla\n\\echo hello :tmp_var\n\\unset foo\n\\echo world :tmp_var\n",
+ # qr/hello foobla.*world :tmp_var.*postgres=\# /s ],
+ [ "\\password\n", qr/Enter new password: / ],
+ [ "foo\n", qr/Enter it again: / ],
+ [ "bla\n", qr/Passwords didn't match.*postgres=\# /s ],
+ [ "exit\n" ],
+ ],
+ 'ipsql basic', 1);
+
+# prompt
+ipsql('-P pager', 0, 5,
+ [ # default prompts
+ [ "SELECT 1 +\n", qr/postgres-\# / ],
+ [ "(2 * \n", qr/postgres\(\# / ],
+ [ "3) + '\n", qr/postgres\'\# / ],
+ [ "4' + \$\$\n", qr/postgres\$\# /],
+ [ "5\$\$ AS \"\n", qr/postgres\"\# / ],
+ [ "seize\" /*\n", qr/postgres\*\# / ],
+ [ " ignored */;\n", qr/\bseize\b.*\b16\b.*postgres=\# /s ],
+ [ "\\if false\n", qr/postgres\@\# / ],
+ [ "\\echo IGNORED\n", qr/command ignored.*if block.*postgres\@\# /s ],
+ # BUG: prompt with - instead of @
+ [ "SELECT 1;\n", qr/query ignored.*if block.*postgres.\# /s ],
+ [ "\\endif\n", qr/postgres=\# / ],
+ [ "\\set SINGLELINE ON\n", qr/postgres\^\# / ],
+ [ "\\set SINGLELINE OFF\n", qr/postgres=\# / ],
+ [ "COPY regress_psql_tap_1_t1(data) FROM STDIN;\n", qr/>> / ],
+ [ "hello\nworld\n\\.\n", qr/COPY 2\b.*postgres=\# /s ],
+ # set new prompts (not tested: %`)
+ [ "\\set PROMPT1 '%%%[%]%?%M%m%>%n%/%~%#%p%R%x%l%:QUIET:%040'\n", qr/%.*\d+(on|off) /s ],
+ [ "\\set PROMPT1 '1> '\n\\set PROMPT2 '2> '\n\\set PROMPT3 '3> '\n", qr/\b1> / ],
+ [ "COPY regress_psql_tap_1_t1(data)\n", qr/\b2> / ],
+ [ "FROM STDIN;\n", qr/\b3> / ],
+ [ "calvin\nhobbes\nsusie\nmoe\n\\.\n", qr/COPY 4\b.*\b1> /s ],
+ # reset prompts
+ [ "\\set PROMPT1 '%/%R%# '\n", qr/PROMPT1.*postgres=# /s ],
+ [ "\\set PROMPT2 '%/%R%# '\n", qr/PROMPT2.*postgres=# /s ],
+ [ "\\set PROMPT3 '>> '\n", qr/PROMPT3.*postgres=# /s ],
+ ],
+ 'ipsql prompt 1');
+
+goto END_HISTORY_ZONE
+ unless $has_history;
+
+# signals
+my $stop = $is_like_unix ? "\004" : "\003";
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ $stop, qr/\\q/ ],
+ ],
+ 'ipsql signals 1', 1);
+
+psql('', 0, "\\s /dev/null\n", $EMPTY, $EMPTY, 'psql \s null');
+
+# tab-completion tests
+ipsql('-P pager', 0, 5,
+ [ # commands
+ [ "SEL\t", qr/SELECT / ],
+ [ "* FROM pg_catalog.pg_ca\t", qr/pg_catalog\.pg_cast / ],
+ [ "LIMIT 17;\n", qr/\b17 rows.*postgres=\# /s ],
+ # backslash commands
+ [ "\\d\t", qr/(\\da.*\\dy.*postgres=# )?\\d/s ],
+ [ "y\n", qr/List of event triggers.*postgres=\# /s ],
+ # pset
+ [ "\\pset \t", qr/(unicode_column_linestyle.*postgres=\# )?\\pset/s ],
+ [ "tu\t", qr/tuples_only / ],
+ [ "off\n", qr/postgres=\# / ],
+ [ "\\pset unicode_b\t", qr/unicode_border_linestyle / ],
+ [ "\t", qr/(double.*single.*postgres=\# )?/s ],
+ [ "double\n", qr/Unicode border line style is "double".*postgres=\# /s ],
+ # set
+ [ "\\set VERB\t", qr/VERBOSITY / ],
+ [ "\t", qr/(default.*verbose.*postgres=\# )?/s ],
+ [ "d\t\n", qr/default.*postgres=\# /s ],
+ # misc
+ [ "\\cd \t", qr/(.*postgres=\# )?\\cd /s ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # object type completion,
+ # strange: the list is not seen from the test driver
+ # but the coverage works as expected.
+ [ "CREATE \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "DROP \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ALTER \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ANALYZE (\t", qr/(SKIP_LOCKED.*postgres=\# )?ANALYZE \(/s ],
+ [ "V\tOF\t", qr/VERBOSE OFF/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # SET
+ [ "SET \t", qr/(zero_damaged_pages.*postgres=\# )?SET /s ],
+ [ "da\t", qr/datestyle / ],
+ [ "\t", qr/TO / ],
+ [ "\tDE\t", qr/DEFAULT/ ],
+ [ ";\n", qr/SET.*postgres=\# /s ],
+ # psql variables
+ [ "\\ec\t", qr/\\echo / ],
+ [ ":q\t", qr/QUIET/ ],
+ [ ":'PO\t", qr/:'PORT'/ ],
+ [ ":\"HISTC\t", qr/:"HISTCONTROL"/ ],
+ [ "\n", qr/(on|off).*'\d+'.*"[a-z]+".*postgres=\# /s ],
+ # file
+ [ "COPY regress_psql_tap_1_t1 FROM \t", qr/(postgres=\# COPY .* FROM )?/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ ],
+ 'ipsql tab 1');
+
+END_HISTORY_ZONE:
+
+# final cleanup
+psql('', 0, '-- cleanup objects
+DROP VIEW regress_psql_tap_1_v1;
+DROP FUNCTION regress_psql_tap_1_f1;
+DROP TABLE regress_psql_tap_1_t1;
+DROP USER regress_psql_tap_1;
+', [ qr{DROP VIEW}, qr{DROP FUNCTION}, qr{DROP TABLE}, qr{DROP ROLE} ], [ qr{^$} ], 'psql -- drop stuff');
+
+$node->stop();
+done_testing();
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index b685b35282..2692c99e17 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -92,6 +92,7 @@ $node->issues_sql_like(
$node->command_checks_all(
[ 'vacuumdb', '--analyze', '--table', 'vacview', 'postgres' ],
0,
+ '',
[qr/^.*vacuuming database "postgres"/],
[qr/^WARNING.*cannot vacuum non-tables or special system tables/s],
'vacuumdb with view');
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 270bd6c856..9c28bec4a7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1676,6 +1676,28 @@ sub command_checks_all
=pod
+=item $node->icommand_checks(cmd, ...)
+
+TestLib::icommand_checks with our connection parameters.
+See command_ok(...)
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local $ENV{PGHOST} = $self->host;
+ local $ENV{PGPORT} = $self->port;
+
+ TestLib::icommand_checks(@_);
+ return;
+}
+
+=pod
+
=item $node->issues_sql_like(cmd, expected_sql, test_name)
Run a command on the node, then verify that $expected_sql appears in the
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 92199792eb..696ab8d403 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -53,6 +53,7 @@ use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
+use Expect;
# specify a recent enough version of Test::More to support the
# done_testing() function
@@ -81,6 +82,7 @@ our @EXPORT = qw(
command_like_safe
command_fails_like
command_checks_all
+ icommand_checks
$windows_os
);
@@ -792,6 +794,8 @@ Arguments:
=item C<ret>: Expected exit code
+=item C<in>: Stdin for command
+
=item C<out>: Expected stdout from command
=item C<err>: Expected stderr from command
@@ -806,12 +810,13 @@ sub command_checks_all
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($cmd, $expected_ret, $out, $err, $test_name) = @_;
+ my ($cmd, $expected_ret, $in, $out, $err, $test_name) = @_;
+ $in = '' if not defined $in;
# run command
my ($stdout, $stderr);
print("# Running: " . join(" ", @{$cmd}) . "\n");
- IPC::Run::run($cmd, '>', \$stdout, '2>', \$stderr);
+ IPC::Run::run($cmd, '<', \$in, '>', \$stdout, '2>', \$stderr);
# See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
my $ret = $?;
@@ -840,6 +845,80 @@ sub command_checks_all
=pod
+=item icommand_checks(cmd, ret, timeout, init, inout, name)
+
+Run a command and check its status and outputs.
+Arguments:
+
+=over
+
+=item C<cmd> ref to list for command, options and arguments to run
+
+=item C<ret> expected exit status
+
+=item C<timeout> for interactions
+
+=item C<init> initial output before interacting
+
+=item C<inout> [ [ input, list of output checks ], ... ]
+
+=item C<name> of the test
+
+=back
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($cmd, $expected_ret, $timeout, $init, $inout, $name) = @_;
+
+ my $ps = Expect->spawn(@$cmd);
+ ok(defined $ps, "$name command spawn: @$cmd");
+ return unless defined $ps;
+
+ $ps->slave->stty(qw(raw));
+ my $n = 0;
+ for my $test (@$inout)
+ {
+ my ($in, @out) = @$test;
+ $n++;
+ $ps->send($in);
+ for my $o (@out)
+ {
+ my $ok = $ps->expect($timeout, '-re', $o);
+ # check header on first output
+ if (defined $init)
+ {
+ # beware of race conditions...
+ ok($ps->before =~ $init,
+ "$name header matches /$init/");
+ undef $init;
+ }
+ ok($ok, "$n: $name output matches /$o/");
+ if (not $ok)
+ {
+ print STDERR "last input: ###$in###\n";
+ print STDERR "before: ###" . $ps->before . "###\n" if $ps->before;
+ print STDERR "match: ###" . $ps->match . "###\n" if $ps->match;
+ print STDERR "after: ###" . $ps->after . "###\n" if $ps->after;
+ print STDERR "error: ###" . $ps->error . "###\n" if $ps->error;
+ }
+ }
+ }
+ $ps->soft_close();
+
+ # check status
+ my $ret = $ps->exitstatus;
+ ok($ret == $expected_ret,
+ "$name status (got $ret vs expected $expected_ret)");
+
+ return;
+}
+
+=pod
+
=back
=cut
I think the TestLib.pm changes should be done separately, not together
with the rest of the hacking in this patch.
Mostly, because I think they're going to cause trouble. Adding a
parameter in the middle of the list may cause trouble for third-party
users of TestLib. I propose that we make the routines a bit smarter to
cope with the API change: use named parameters instead. And in order to
do that without having to change existing users of command_check, make
it so that the routine checks whether the parameter is a hashref, and
behave differently. So when called as in the existing callsites (five
scalar paramters) it behaves as currently.
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Thu, Sep 12, 2019 at 12:14:16PM -0300, Alvaro Herrera wrote:
Mostly, because I think they're going to cause trouble. Adding a
parameter in the middle of the list may cause trouble for third-party
users of TestLib. I propose that we make the routines a bit smarter to
cope with the API change: use named parameters instead. And in order to
do that without having to change existing users of command_check, make
it so that the routine checks whether the parameter is a hashref, and
behave differently. So when called as in the existing callsites (five
scalar parameters) it behaves as currently.
+1.
--
Michael
On 2019-Sep-13, Fabien COELHO wrote:
Hello Alvaro,
I think the TestLib.pm changes should be done separately, not together
with the rest of the hacking in this patch.Mostly, because I think they're going to cause trouble. Adding a
parameter in the middle of the list may cause trouble for third-party
users of TestLib.That is also what I thought, however, see below.
I see. But you seem to have skipped my suggestion without considering
it.
I think the current API of these functions where they just receive a
plain array of arguments, and all callers have to be patched in unison,
is not very convenient. Also, I *think* your new icommand_checks method
is the same as command_checks_all, except that you also have the "init"
part. So you're duplicating code because the original doesn't have
functionality you need? But why do that, if you could have *one*
function that does both things? If some callers don't have the "init"
part, just omit it from the parameters.
(Whether it's implemented using Expect or not should not matter. Either
Expect works everywhere, and we can use it, or it doesn't and we can't.)
--
�lvaro Herrera https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
Import Notes
Reply to msg id not found: alpine.DEB.2.21.1909130906210.20765@lancre | Resolved by subject fallback
Hello Alvaro,
I think the TestLib.pm changes should be done separately, not together
with the rest of the hacking in this patch.Mostly, because I think they're going to cause trouble. Adding a
parameter in the middle of the list may cause trouble for third-party
users of TestLib.That is also what I thought, however, see below.
I see. But you seem to have skipped my suggestion without considering
it.
I did understand it, but as Tom did not want simple hocus-pocus, ISTM that
dynamically checking the argument type would not be considered a very good
idea either.
I think the current API of these functions where they just receive a
plain array of arguments, and all callers have to be patched in unison,
is not very convenient.
I agree, but the no diff solution was rejected. I can bring one back, but
going against Tom's views has not proven a good move in the past.
Also, I *think* your new icommand_checks method is the same as
command_checks_all, except that you also have the "init" part.
Nope, it is an interactive version based on Expect, which sends input and
waits for output, the process is quite different from a simple one shot no
timeout exec version.
So you're duplicating code because the original doesn't have
functionality you need?
Yes, I'm creating a interactive validation variant.
But why do that, if you could have *one* function that does both things?
If some callers don't have the "init" part, just omit it from the
parameters.
Although it could be abstracted somehow, I do not think that having one
function behaving so differently under the hood is a good idea. It is not
just the question of the init part.
(Whether it's implemented using Expect or not should not matter. Either
Expect works everywhere, and we can use it, or it doesn't and we can't.)
For me the question is not about Expect dependency, it is more about how
the test behaves.
--
Fabien.
On Thu, Sep 12, 2019 at 4:49 PM Fabien COELHO <coelho@cri.ensmp.fr> wrote:
Here is a v5.
Few more in icommand_checks subroutine:
Few unwanted code can be removed.Indeed, more debug and test code.
Attached v6 fixes these, and I checked for remaining scrubs without
finding any.
Few comments:
+ [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ],
+ [ 'TABLE', [ qr{ONLY} ] ], # hmmm...
+ [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ],
+ [ 'UNLISTEN', [ ] ],
We can remove # hmmm... if not required
+ [ 'UPDATE', [ qr{RETURNING} ] ],
+ [ 'VACUUM', [ qr{FREEZE} ] ],
+ [ 'VALUES', [ qr{ORDER BY} ] ],
+ [ 'WITH', [ qr{RECURSIVE} ] ], # SELECT duplicate?
+);
We can remove # SELECT duplicate? if not required
+
+psql('--log-file=/dev/null', 0, "SELECT 5432 AS pg\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql -L null');
+
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) FROM PROGRAM
'echo moe'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy echo');
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) TO PROGRAM 'cat'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy cat'); # :-)
+
+END_UNIX_ZONE:
We can remove # :-) if not required
Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com
Hi Fabien,
On Tue, Sep 17, 2019 at 04:48:06PM +0530, vignesh C wrote:
Few comments: + [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ], + [ 'TABLE', [ qr{ONLY} ] ], # hmmm... + [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ], + [ 'UNLISTEN', [ ] ],We can remove # hmmm... if not required
+ [ 'UPDATE', [ qr{RETURNING} ] ], + [ 'VACUUM', [ qr{FREEZE} ] ], + [ 'VALUES', [ qr{ORDER BY} ] ], + [ 'WITH', [ qr{RECURSIVE} ] ], # SELECT duplicate? +);We can remove # SELECT duplicate? if not required
+ +psql('--log-file=/dev/null', 0, "SELECT 5432 AS pg\n", + [ qr/\b5432\b/ ], $EMPTY, 'psql -L null'); + +psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) FROM PROGRAM 'echo moe'\n", + [ qr/COPY 1\b/ ], $EMPTY, 'psql copy echo'); +psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) TO PROGRAM 'cat'\n", + [ qr/COPY 1\b/ ], $EMPTY, 'psql copy cat'); # :-) + +END_UNIX_ZONE:We can remove # :-) if not required
Please note that you have received comments on this patch a couple of
weeks ago. The patch was still marked as "needs review", which was
incorrect, and it does not apply. Perhaps you did not notice it, so I
am moving it to next CF, waiting on author for a rebase and for
replies on those comments.
--
Michael
Bonjour Michaᅵl,
Please note that you have received comments on this patch a couple of
weeks ago. The patch was still marked as "needs review", which was
incorrect, and it does not apply. Perhaps you did not notice it, so I
am moving it to next CF, waiting on author for a rebase and for
replies on those comments.
Indeed, I did not notice.
Attached a rebase, which also removes the 3 comments pointed out by
Vignesh.
--
Fabien.
Attachments:
psql-tap-7.patchtext/x-diff; name=psql-tap-7.patchDownload
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
index b7d36b65dd..aabc27ab6f 100644
--- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -506,6 +506,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*checksum verification failed/s],
'pg_basebackup reports checksum mismatch');
@@ -526,6 +527,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt2" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*further.*failures.*will.not.be.reported/s],
'pg_basebackup does not report more than 5 checksum mismatches');
@@ -542,6 +544,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start';
$node->command_checks_all(
[ 'pg_basebackup', '-D', "$tempdir/backup_corrupt3" ],
1,
+ '',
[qr{^$}],
[qr/^WARNING.*7 total checksum verification failures/s],
'pg_basebackup correctly report the total number of checksum mismatches');
diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl
index 59228b916c..cf4811d382 100644
--- a/src/bin/pg_checksums/t/002_actions.pl
+++ b/src/bin/pg_checksums/t/002_actions.pl
@@ -62,6 +62,7 @@ sub check_relation_corruption
'--filenode', $relfilenode_corrupted
],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data for single relfilenode on tablespace $tablespace"
@@ -71,6 +72,7 @@ sub check_relation_corruption
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/Bad checksums:.*1/],
[qr/checksum verification failed/],
"fails with corrupted data on tablespace $tablespace");
@@ -203,6 +205,7 @@ sub fail_corrupt
$node->command_checks_all(
[ 'pg_checksums', '--check', '-D', $pgdata ],
1,
+ '',
[qr/^$/],
[qr/could not read block 0 in file.*$file\":/],
"fails for corrupted data in $file");
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
index 3b63ad230f..ebe9b80a52 100644
--- a/src/bin/pg_controldata/t/001_pg_controldata.pl
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -33,6 +33,7 @@ close $fh;
command_checks_all(
[ 'pg_controldata', $node->data_dir ],
0,
+ '',
[
qr/WARNING: Calculated CRC checksum does not match value stored in file/,
qr/WARNING: invalid WAL segment size/
diff --git a/src/bin/pg_resetwal/t/002_corrupted.pl b/src/bin/pg_resetwal/t/002_corrupted.pl
index f9940d7fc5..1990669d26 100644
--- a/src/bin/pg_resetwal/t/002_corrupted.pl
+++ b/src/bin/pg_resetwal/t/002_corrupted.pl
@@ -30,6 +30,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/
@@ -46,6 +47,7 @@ close $fh;
command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
+ '',
[qr/pg_control version number/],
[
qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/
diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl
index 1845869016..680809f98d 100644
--- a/src/bin/pgbench/t/001_pgbench_with_server.pl
+++ b/src/bin/pgbench/t/001_pgbench_with_server.pl
@@ -50,7 +50,7 @@ sub pgbench
push @cmd, @args;
- $node->command_checks_all(\@cmd, $stat, $out, $err, $name);
+ $node->command_checks_all(\@cmd, $stat, '', $out, $err, $name);
# cleanup?
#unlink @filenames or die "cannot unlink files (@filenames): $!";
diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl
index 8b6d442812..30dc51952d 100644
--- a/src/bin/pgbench/t/002_pgbench_no_server.pl
+++ b/src/bin/pgbench/t/002_pgbench_no_server.pl
@@ -25,7 +25,7 @@ sub pgbench
my ($opts, $stat, $out, $err, $name) = @_;
print STDERR "opts=$opts, stat=$stat, out=$out, err=$err, name=$name";
command_checks_all([ 'pgbench', split(/\s+/, $opts) ],
- $stat, $out, $err, $name);
+ $stat, '', $out, $err, $name);
return;
}
@@ -52,7 +52,7 @@ sub pgbench_scripts
push @cmd, '-f', $filename;
}
}
- command_checks_all(\@cmd, $stat, $out, $err, $name);
+ command_checks_all(\@cmd, $stat, '', $out, $err, $name);
return;
}
diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore
index c2862b12d6..d324c1c1fa 100644
--- a/src/bin/psql/.gitignore
+++ b/src/bin/psql/.gitignore
@@ -3,3 +3,4 @@
/sql_help.c
/psql
+/tmp_check
diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile
index 42771bcca8..af783923df 100644
--- a/src/bin/psql/Makefile
+++ b/src/bin/psql/Makefile
@@ -73,8 +73,15 @@ uninstall:
clean distclean:
rm -f psql$(X) $(OBJS) lex.backup
+ rm -rf tmp_check
# files removed here are supposed to be in the distribution tarball,
# so do not clean them in the clean/distclean rules
maintainer-clean: distclean
rm -f sql_help.h sql_help.c psqlscanslash.c
+
+check:
+ $(prove_check)
+
+installcheck:
+ $(prove_installcheck)
diff --git a/src/bin/psql/t/001_psql.pl b/src/bin/psql/t/001_psql.pl
new file mode 100644
index 0000000000..a96527f250
--- /dev/null
+++ b/src/bin/psql/t/001_psql.pl
@@ -0,0 +1,856 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More;
+
+my $node = get_new_node('main');
+$node->init();
+$node->start();
+
+# create a file under the test directory
+# return its full path
+sub create_test_file
+{
+ my ($fname, $contents) = @_;
+ my $fn = $node->basedir . '/' . $fname;
+ append_to_file($fn, $contents);
+ return $fn;
+}
+
+# invoke psql
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected exit status
+# - in: input stream
+# - out: list of re to check on stdout
+# - err: list of re to check on stderr
+# - name: of the test
+# - more: more raw arguments
+sub psql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $in, $out, $err, $name, @more) = @_;
+ $opts =~ s/^!// or push @more, '-X';
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+ $node->command_checks_all(\@cmd, $stat, $in, $out, $err, $name);
+ return;
+}
+
+# invoke psql interactively, making it believe it is connected to a tty
+# - opts: space-separated options and arguments
+# -X is appended, unless opts starts with !
+# - stat: expected final exit status
+# - timeout: for interactions, in second
+# - inout: list of input expected re
+# - name: of the test
+# - remove: tell to remove init/start/end checks
+sub ipsql
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+ my ($opts, $stat, $timeout, $inout, $name, $remove) = @_;
+
+ # build command to run
+ my @more = ();
+ @more = ('-X') unless $opts =~ /^!/;
+ $opts =~ s/^!//;
+ my @cmd = ('psql', split(/\s+/, $opts), @more);
+
+ # check it is running at start and end, and exit cleanly
+ unless ($remove)
+ {
+ unshift @$inout, [ "\\echo STARTING :VERSION_NUM\n", qr/STARTING \d+.*postgres=\# /s ];
+ push @$inout, [ "\\echo ENDING :VERSION_NUM\\q\n", qr/ENDING \d+$/s ];
+ }
+
+ # call the stuff
+ $node->icommand_checks(
+ \@cmd, $stat, $timeout,
+ $remove ? undef : qr/psql .*Type "help" for help..*postgres=# /s,
+ $inout, $name);
+ return;
+}
+
+#
+# check whether we have readline/history capability
+#
+my ($out, $err);
+$node->psql('postgres', "\\s\n", 'stdout' => \$out, 'stderr' => \$err);
+my $has_history = $err !~ /history is not supported by this installation/;
+
+my $EMPTY = [ qr/^$/ ];
+
+#
+# check various options
+#
+# this cannot be tested from within a SQL script
+psql('!--help', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql --help');
+psql('!-?', 0, '',
+ [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ],
+ $EMPTY, 'psql -?');
+psql('!--version', 0, '',
+ [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql --version');
+psql('--help=variables', 0, '',
+ [ qr{VERSION_NAME}, qr{unicode_border_linestyle}, qr{PSQL_EDITOR} ],
+ $EMPTY, 'psql --help=variables');
+# look for plenty of backslash-commands
+psql('--help=commands', 0, '',
+ [ qr{\\copyright}, qr{\\pset}, qr{(?!VERSION_NAME)} ],
+ $EMPTY, 'psql --help=commands');
+
+# tested elsewhere: -a -A -d -e -h -L
+psql('-b', 0, "SELECT 1/0;\n",
+ $EMPTY, [ qr{STATEMENT: SELECT 1/0}, qr{ERROR: division by zero} ], 'psql -b');
+psql('-c \\q', 0, '', $EMPTY, $EMPTY, 'psql -c');
+psql('-c \\l -c \\l', 0, '', [ qr{template0.*template0}s ], $EMPTY, 'psql -c -c');
+psql('', 0, '', [ qr{(?!1234)}, qr{5678}s ], $EMPTY, 'psql -c ;;', ('-c', 'SELECT 1234; SELECT 5678;'));
+psql('-E', 0, "\\l\n", [ qr{\* QUERY \*}, qr{template0} ], $EMPTY, 'psql -E');
+psql('-A -F _', 0, "SELECT 54, 32;\n", [ qr{54_32} ], $EMPTY, 'psql -F _');
+psql('-H', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -H');
+psql('-l', 0, '', [ qr{template0}, qr{template1} ], $EMPTY, 'psql -l');
+psql('-n', 0, "\\q\n", $EMPTY, $EMPTY, 'psql -n');
+psql('-P format=html', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -P format=html');
+psql('-P tuples_only', 0, "\\pset\n", [ qr{tuples_only.*on} ], $EMPTY, 'psql -P tuples_only');
+psql('-A -R_', 0, "VALUES (1), (2);\n", [ qr{_1_2_} ], $EMPTY, 'psql -R _');
+psql('-s', 0, "SELECT 1;\n", [ qr{verify command}, qr{SELECT 1}, qr{press return to proceed} ], $EMPTY, 'psql -s');
+psql('-S', 0, "SELECT 1 AS one\nSELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], $EMPTY, 'psql -S');
+psql('-H -T width=100', 0, "SELECT 1 AS one;\n", [ qr{table.*width=100} ], $EMPTY, 'psql -T ...');
+psql('-X -V', 0, '', [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql -X -V');
+psql('', 0, "\\echo :VERSION_NUM\n", [ qr{\d{6}} ], $EMPTY, 'psql -- show VERSION_NUM');
+psql('-v VERSION_NUM', 0, "\\echo :VERSION_NUM\n", [ qr{:VERSION_NUM} ], $EMPTY, 'psql -v ...');
+psql('-v VERSION_NUM=bla', 0, "\\echo :VERSION_NUM\n", [ qr{bla} ], $EMPTY, 'psql -v ...=.');
+psql('-w', 0, '', $EMPTY, $EMPTY, 'psql -w');
+psql('-x', 0, "SELECT 1 AS one, 2 AS two;\n", [ qr{one \| 1.*two \| 2}s ], $EMPTY, 'psql -x');
+psql('-1', 0, "SELECT 54;\nSELECT 32;\n", [ qr{54}, qr{32} ], $EMPTY, 'psql -1');
+psql('-X -?', 0, '',
+ [ qr{interactive terminal}, qr{Usage:}, qr{Connection options:} ], $EMPTY, 'psql -X -?');
+psql('--csv', 0, "SELECT 1 AS one, 2 AS two\n", [ qr{one,two}, qr{1,2} ], $EMPTY, 'psql --csv');
+
+#
+# create some objects to help \d tests later
+#
+psql('', 0, '-- create some objects
+CREATE USER regress_psql_tap_1;
+ALTER ROLE regress_psql_tap_1 SET enable_indexscan TO off;
+CREATE TABLE regress_psql_tap_1_t1(data TEXT NOT NULL);
+CREATE FUNCTION regress_psql_tap_1_f1() RETURNS INTEGER AS $$ SELECT 54321 $$ LANGUAGE SQL;
+CREATE VIEW regress_psql_tap_1_v1 AS SELECT 54321 AS one;
+', [ qr{CREATE ROLE}, qr{ALTER ROLE} ], [ qr{^$} ], 'psql -- create stuff');
+
+#
+# check interactive help
+#
+# this is mostly for coverage, including occasional negatives.
+# we certainly do not want to have an output file with the whole doc.
+my @backslash_help = (
+ # \h command, list of stdout matches
+ [ 'no-help', [ qr{No help available for} ] ],
+ [ '' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ [ ' ' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ],
+ # lists
+ [ 'ALTER', [ qr{AGGREGATE}, qr{ROLE}, qr{VIEW}, qr{(?!ACCESS METHOD)}, qr{(?!TRANSFORM)} ] ],
+ [ 'ALTER TEXT SEARCH', [ qr{CONFIGURATION}, qr{DICTIONARY}, qr{PARSER}, qr{TEMPLATE} ] ],
+ [ 'ALTER FOREIGN', [ qr{DATA WRAPPER}, qr{TABLE} ] ],
+ [ 'CREATE', [ qr{ACCESS METHOD}, qr{TRANSFORM} ] ],
+ [ 'DROP', [ qr{ACCESS METHOD}, qr{VIEW} ] ],
+ # details
+ [ 'ABORT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'ALTER AGGREGATE', [ qr{RENAME TO}, qr{(?!CONVERSION)} ] ],
+ [ 'ALTER CONVERSION', [ qr{SET SCHEMA}, qr{(?!AGGREGATE)} ] ],
+ [ 'ALTER COLLATION', [ qr{REFRESH VERSION} ] ],
+ [ 'ALTER DATABASE', [ qr{CONNECTION LIMIT} ] ],
+ [ 'ALTER DEFAULT PRIVILEGES', [ qr{GRANT} ] ],
+ [ 'ALTER DOMAIN', [ qr{VALIDATE CONSTRAINT} ] ],
+ [ 'ALTER EVENT TRIGGER', [ qr{DISABLE} ] ],
+ [ 'ALTER EXTENSION', [ qr{OPERATOR FAMILY} ] ],
+ [ 'ALTER FOREIGN DATA WRAPPER', [ qr{VALIDATOR} ] ],
+ [ 'ALTER FOREIGN TABLE', [ qr{SET STORAGE} ] ],
+ [ 'ALTER FUNCTION', [ qr{PARALLEL} ] ],
+ [ 'ALTER GROUP', [ qr{DROP USER} ] ],
+ [ 'ALTER INDEX', [ qr{ALL IN TABLESPACE} ] ],
+ [ 'ALTER LANGUAGE', [ qr{PROCEDURAL}, qr{(?!INDEX)} ] ],
+ [ 'ALTER LARGE OBJECT', [ qr{OWNER TO} ] ],
+ [ 'ALTER MATERIALIZED VIEW', [ qr{SET TABLESPACE} ] ],
+ [ 'ALTER OPERATOR', [ qr{JOIN =} ] ],
+ [ 'ALTER OPERATOR CLASS', [ qr{SET SCHEMA}, qr{(?!LANGUAGE)} ] ],
+ [ 'ALTER OPERATOR FAMILY', [ qr{FUNCTION} ] ],
+ [ 'ALTER POLICY', [ qr{WITH CHECK} ] ],
+ [ 'ALTER PROCEDURE', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER PUBLICATION', [ qr{DROP TABLE} ] ],
+ [ 'ALTER ROLE', [ qr{BYPASSRLS} ] ],
+ [ 'ALTER ROUTINE', [ qr{LEAKPROOF} ] ],
+ [ 'ALTER RULE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER SCHEMA', [ qr{OWNER TO} ] ],
+ [ 'ALTER SEQUENCE', [ qr{NO MAXVALUE} ] ],
+ [ 'ALTER SERVER', [ qr{VERSION} ] ],
+ [ 'ALTER STATISTICS', [ qr{SET SCHEMA} ] ],
+ [ 'ALTER SUBSCRIPTION', [ qr{REFRESH PUBLICATION} ] ],
+ [ 'ALTER SYSTEM', [ qr{RESET ALL} ] ],
+ [ 'ALTER TABLE', [ qr{ALL IN TABLESPACE}, qr{DISABLE ROW LEVEL SECURITY} ] ],
+ [ 'ALTER TABLESPACE', [ qr{RESET} ] ],
+ [ 'ALTER TEXT SEARCH CONFIGURATION', [ qr{ALTER MAPPING FOR} ] ],
+ [ 'ALTER TEXT SEARCH DICTIONARY', [ qr{OWNER TO} ] ],
+ [ 'ALTER TEXT SEARCH PARSER', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TEXT SEARCH TEMPLATE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ],
+ [ 'ALTER TRIGGER', [ qr{DEPENDS ON EXTENSION} ] ],
+ [ 'ALTER TYPE', [ qr{RENAME ATTRIBUTE} ] ],
+ [ 'ALTER USER', [ qr{NOREPLICATION}, qr{(?!MAPPING)} ] ],
+ [ 'ALTER USER MAPPING', [ qr{SERVER} ] ],
+ [ 'ALTER VIEW', [ qr{DROP DEFAULT} ] ],
+ [ 'ANALYZE', [ qr{VERBOSE}, qr{SKIP_LOCKED} ] ],
+ [ 'BEGIN', [ qr{ISOLATION LEVEL} ] ],
+ [ 'CALL', [] ],
+ [ 'CHECKPOINT', [] ],
+ [ 'CLOSE', [ qr{ALL} ] ],
+ [ 'CLUSTER', [ qr{VERBOSE} ] ],
+ [ 'COMMENT', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'COMMIT', [ qr{TRANSACTION}, qr{CHAIN} ] ],
+ [ 'COMMIT PREPARED', [] ],
+ [ 'COPY', [ qr{FORMAT}, qr{FORCE_NOT_NULL} ] ],
+ [ 'CREATE ACCESS METHOD', [ qr{HANDLER} ] ],
+ [ 'CREATE AGGREGATE', [ qr{SSPACE}, qr{SORTOP}, qr{FINALFUNC} ] ],
+ [ 'CREATE CAST', [ qr{AS IMPLICIT} ] ],
+ [ 'CREATE COLLATION', [ qr{LC_CTYPE}, qr{DETERMINISTIC} ] ],
+ [ 'CREATE CONVERSION', [ qr{FOR} ] ],
+ [ 'CREATE DATABASE', [ qr{ALLOW_CONNECTIONS} ] ],
+ [ 'CREATE DOMAIN', [ qr{COLLATE} ] ],
+ [ 'CREATE EVENT TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE EXTENSION', [ qr{FROM} ] ],
+ [ 'CREATE FOREIGN DATA WRAPPER', [ qr{NO VALIDATOR} ] ],
+ [ 'CREATE FOREIGN TABLE', [ qr{SERVER} ] ],
+ [ 'CREATE FUNCTION', [ qr{CALLED ON NULL INPUT}, qr{(?!PROCEDURE)} ] ],
+ [ 'CREATE GROUP', [ qr{ADMIN} ] ],
+ [ 'CREATE INDEX', [ qr{CONCURRENTLY}, qr{WHERE} ] ],
+ [ 'CREATE LANGUAGE', [ qr{TRUSTED} ] ],
+ [ 'CREATE MATERIALIZED VIEW', [ qr{DATA} ] ],
+ [ 'CREATE OPERATOR', [ qr{LEFTARG} ] ],
+ [ 'CREATE OPERATOR CLASS', [ qr{FOR ORDER BY} ] ],
+ [ 'CREATE OPERATOR FAMILY', [ qr{USING} ] ],
+ [ 'CREATE POLICY', [ qr{PERMISSIVE} ] ],
+ [ 'CREATE PROCEDURE', [ qr{SECURITY INVOKER}, qr{(?!FUNCTION)} ] ],
+ [ 'CREATE PUBLICATION', [ qr{FOR ALL TABLES} ] ],
+ [ 'CREATE ROLE', [ qr{SYSID} ] ],
+ [ 'CREATE RULE', [ qr{ALSO} ] ],
+ [ 'CREATE SCHEMA', [ qr{AUTHORIZATION} ] ],
+ [ 'CREATE SEQUENCE', [ qr{NO MINVALUE} ] ],
+ [ 'CREATE SERVER', [ qr{FOREIGN DATA WRAPPER} ] ],
+ [ 'CREATE STATISTICS', [ qr{FROM} ] ],
+ [ 'CREATE SUBSCRIPTION', [ qr{CONNECTION} ] ],
+ [ 'CREATE TABLE', [ qr{PARTITION BY} ] ],
+ [ 'CREATE TABLE AS', [ qr{ON COMMIT} ] ],
+ [ 'CREATE TABLESPACE', [ qr{LOCATION} ] ],
+ [ 'CREATE TEXT SEARCH CONFIGURATION', [ qr{PARSER} ] ],
+ [ 'CREATE TEXT SEARCH DICTIONARY', [ qr{TEMPLATE} ] ],
+ [ 'CREATE TEXT SEARCH PARSER', [ qr{GETTOKEN} ] ],
+ [ 'CREATE TEXT SEARCH TEMPLATE', [ qr{LEXIZE} ] ],
+ [ 'CREATE TRANSFORM', [ qr{FROM SQL WITH FUNCTION} ] ],
+ [ 'CREATE TRIGGER', [ qr{EXECUTE} ] ],
+ [ 'CREATE TYPE', [ qr{SUBTYPE} ] ],
+ [ 'CREATE USER', [ qr{PASSWORD NULL} ] ],
+ [ 'CREATE USER MAPPING', [ qr{SERVER} ] ],
+ [ 'CREATE VIEW', [ qr{CASCADED} ] ],
+ [ 'DEALLOCATE', [ qr{PREPARE} ] ],
+ [ 'DECLARE', [ qr{INSENSITIVE} ] ],
+ [ 'DELETE', [ qr{WHERE CURRENT OF} ] ],
+ [ 'DISCARD', [ qr{PLANS} ] ],
+ [ 'DO', [ qr{LANGUAGE} ] ],
+ [ 'DROP ACCESS METHOD', [ qr{CASCADE} ] ],
+ [ 'DROP AGGREGATE', [ qr{ORDER BY} ] ],
+ [ 'DROP CAST', [ qr{RESTRICT} ] ],
+ [ 'DROP COLLATION', [ qr{RESTRICT} ] ],
+ [ 'DROP CONVERSION', [ qr{RESTRICT} ] ],
+ [ 'DROP DATABASE', [ qr{IF EXISTS} ] ],
+ [ 'DROP DOMAIN', [ qr{RESTRICT} ] ],
+ [ 'DROP EVENT TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP EXTENSION', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN DATA WRAPPER', [ qr{RESTRICT} ] ],
+ [ 'DROP FOREIGN TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP FUNCTION', [ qr{RESTRICT} ] ],
+ [ 'DROP GROUP', [ qr{IF EXISTS} ] ],
+ [ 'DROP INDEX', [ qr{RESTRICT} ] ],
+ [ 'DROP LANGUAGE', [ qr{RESTRICT} ] ],
+ [ 'DROP MATERIALIZED VIEW', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR CLASS', [ qr{RESTRICT} ] ],
+ [ 'DROP OPERATOR FAMILY', [ qr{RESTRICT} ] ],
+ [ 'DROP OWNED', [ qr{CURRENT_USER} ] ],
+ [ 'DROP POLICY', [ qr{RESTRICT} ] ],
+ [ 'DROP PROCEDURE', [ qr{RESTRICT} ] ],
+ [ 'DROP PUBLICATION', [ qr{RESTRICT} ] ],
+ [ 'DROP ROLE', [ qr{IF EXISTS} ] ],
+ [ 'DROP ROUTINE', [ qr{RESTRICT} ] ],
+ [ 'DROP RULE', [ qr{RESTRICT} ] ],
+ [ 'DROP SCHEMA', [ qr{RESTRICT} ] ],
+ [ 'DROP SEQUENCE', [ qr{RESTRICT} ] ],
+ [ 'DROP SERVER', [ qr{RESTRICT} ] ],
+ [ 'DROP STATISTICS', [ qr{IF EXISTS} ] ],
+ [ 'DROP SUBSCRIPTION', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLE', [ qr{RESTRICT} ] ],
+ [ 'DROP TABLESPACE', [ qr{IF EXISTS} ] ],
+ [ 'DROP TEXT SEARCH CONFIGURATION', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH DICTIONARY', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH PARSER', [ qr{RESTRICT} ] ],
+ [ 'DROP TEXT SEARCH TEMPLATE', [ qr{RESTRICT} ] ],
+ [ 'DROP TRANSFORM', [ qr{LANGUAGE} ] ],
+ [ 'DROP TRIGGER', [ qr{RESTRICT} ] ],
+ [ 'DROP TYPE', [ qr{RESTRICT} ] ],
+ [ 'DROP USER', [ qr{IF EXISTS} ] ],
+ [ 'DROP USER MAPPING', [ qr{SERVER} ] ],
+ [ 'DROP VIEW', [ qr{RESTRICT} ] ],
+ [ 'END', [ qr{TRANSACTION}, qr{CHAIN}, qr{(?!COMMIT)} ] ],
+ [ 'EXECUTE', [ ] ],
+ [ 'EXPLAIN', [ qr{VERBOSE} ] ],
+ [ 'FETCH', [ qr{RELATIVE} ] ],
+ [ 'GRANT', [ qr{ALL TABLES} ] ],
+ [ 'IMPORT FOREIGN SCHEMA', [ qr{FROM SERVER} ] ],
+ [ 'INSERT', [ qr{ON CONFLICT} ] ],
+ [ 'LISTEN', [ ] ],
+ [ 'LOAD', [ ] ],
+ [ 'LOCK', [ qr{NOWAIT} ] ],
+ [ 'MOVE', [ qr{FORWARD} ] ],
+ [ 'NOTIFY', [ ] ],
+ [ 'PREPARE', [ qr{AS} ] ],
+ [ 'PREPARE TRANSACTION', [ ] ],
+ [ 'REASSIGN OWNED', [ qr{SESSION_USER} ] ],
+ [ 'REFRESH MATERIALIZED VIEW', [ qr{CONCURRENTLY} ] ],
+ [ 'REINDEX', [ qr{DATABASE} ] ],
+ [ 'RELEASE', [ qr{SAVEPOINT} ] ],
+ [ 'RESET', [ qr{RESET ALL} ] ],
+ [ 'REVOKE', [ qr{PRIVILEGES} ] ],
+ [ 'ROLLBACK', [ qr{CHAIN} ] ],
+ [ 'ROLLBACK PREPARED', [ ]],
+ [ 'SAVEPOINT', [ ] ],
+ [ 'SECURITY LABEL', [ qr{SUBSCRIPTION} ] ],
+ [ 'SELECT', [ qr{RECURSIVE} ] ],
+ [ 'SET', [ qr{SESSION} ] ],
+ [ 'SET CONSTRAINTS', [ qr{IMMEDIATE} ] ],
+ [ 'SET ROLE', [ qr{ROLE NONE} ] ],
+ [ 'SET SESSION AUTHORIZATION', [ qr{DEFAULT} ] ],
+ [ 'SET TRANSACTION', [ qr{SNAPSHOT} ] ],
+ [ 'SHOW', [ qr{SHOW ALL} ] ],
+ [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ],
+ [ 'TABLE', [ qr{ONLY} ] ],
+ [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ],
+ [ 'UNLISTEN', [ ] ],
+ [ 'UPDATE', [ qr{RETURNING} ] ],
+ [ 'VACUUM', [ qr{FREEZE} ] ],
+ [ 'VALUES', [ qr{ORDER BY} ] ],
+ [ 'WITH', [ qr{RECURSIVE} ] ],
+);
+
+for my $h (@backslash_help)
+{
+ my ($cmd, $out) = @$h;
+ push @$out, qr{$cmd};
+ psql('', 0, "\\h $cmd\n", $out, [ qr{^$} ], "psql -- \\h $cmd");
+}
+
+# special cases
+psql('', 0, "\\h ROLLBACK TO SAVEPOINT\n", [ qr{ROLLBACK}, qr{SAVEPOINT} ],
+ [ qr{^$} ], "psql -- \\h ROLLBACK TO SAVEPOINT");
+psql('', 0, "\\h SELECT INTO\n", [ qr{SELECT}, qr{INTO} ],
+ [ qr{^$} ], "psql -- \\h SELECT INTO");
+
+#
+# check describe and other backslash commands
+#
+# the output can vary significantly, especially with +
+my @backslash_out = (
+ # empty
+ [ "\\d\n", [ qr{List of relations}, qr{regress_psql_tap_1_t1}, qr{(?!pg_locks)} ] ],
+ [ "\\dS\n", [ qr{List of relations}, qr{pg_locks} ] ],
+ # aggregates
+ [ "\\da\n", [ qr{List of aggregate functions}, qr{(?!Description)} ] ],
+ [ "\\da+\n", [ qr{List of aggregate functions}, qr{(Description)} ] ],
+ [ "\\daS\n", [ qr{array_agg}, qr{avg}, qr{bit_and}, qr{bit_or}, qr{count}, qr{max} ] ],
+ # access methods
+ [ "\\dA\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{(?!Description)} ] ],
+ [ "\\dA+\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{Description} ] ],
+ # tablespaces
+ [ "\\db\n", [ qr{List of tablespaces}, qr{pg_default}, qr{pg_global}, qr{(?!Size)} ] ],
+ [ "\\db+ pg_def*\n", [ qr{List of tablespaces}, qr{pg_default}, qr{(?!pg_global)}, qr{Size} ] ],
+ # functions
+ [ "\\df\n", [ qr{List of functions} ] ],
+ [ "\\df+\n", [ qr{List of functions} ] ],
+ [ "\\dftS\n", [ qr{RI_}, qr{Type}, qr{(?!Volatility)}, qr{(?!Parallel)} ] ],
+ [ "\\dftS+\n", [ qr{Volatility}, qr{Parallel} ] ],
+ [ "\\dfwS\n", [ qr{lag}, qr{dense_rank}, qr{(?!Owner)}, qr{(?!Language)} ] ],
+ [ "\\dfwS+\n", [ qr{cume_dist}, qr{Owner}, qr{Language} ] ],
+ [ "\\dfnS\n", [ qr{abbrev}, qr{(?!bit_or)} ] ],
+ [ "\\dfaS\n", [ qr{bit_and}, qr{(?!abs)} ] ],
+ [ "\\dfpS\n", [ qr{List of functions} ] ],
+ [ "\\dfwtS\n", [ qr{RI_FKey_}, qr{last_value}, qr{nth_value}, qr{(?!bit_and)} ] ],
+ # type
+ [ "\\dTS charac*\n", [ qr{List of data types}, qr{character varying}, qr{(?!double)}, qr{(?!Size)} ] ],
+ [ "\\dTS+\n", [ qr{aclitem}, qr{Size} ] ],
+ # operator
+ [ "\\doS\n", [ qr{List of operators}, qr{!~\*} ] ],
+ [ "\\doS+\n", [ qr{<->}, qr{Result type}] ],
+ # databases
+ [ "\\l\n", [ qr{List of databases}, qr{template0}, qr{template1}, qr{(?!Size)} ] ],
+ [ "\\l a*\n", [ qr{(?!template[01])} ] ],
+ [ "\\l+\n", [ qr{Size}, qr{Encoding} ] ],
+ [ "\\list\n", [ qr{List of databases} ] ],
+ # permissions: well tested
+ # default acls (empty)
+ [ "\\ddp\n", [ qr{Default access privileges} ] ],
+ # descriptions (empty)
+ [ "\\dd\n", [ qr{Object descriptions} ] ],
+ # tables: well tested
+ # roles/users/groups
+ [ "\\du\n", [ qr{Superuser}, qr{regress_psql_tap_1}, qr{List of roles}, qr{(?!Description)} ] ],
+ [ "\\du+\n", [ qr{Superuser}, qr{List of roles}, qr{Description} ] ],
+ [ "\\dg\n", [ qr{Superuser}, qr{List of roles} ] ],
+ # role settings
+ [ "\\drds\n", [ qr{List of settings} ] ],
+ [ "\\drds regress_* *\n", [ qr{List of settings} ] ],
+ # index/...
+ [ "\\diS\n", [ qr{List of relations}, qr{pg_am_oid_index} ] ],
+ [ "\\diS+\n", [ qr{Table} ] ],
+ # partition tables: well tested
+ # large objects
+ [ "\\dl", [ qr{Large objects} ] ],
+ # languages
+ [ "\\dL\n", [ qr{List of languages}, qr{plpgsql}, qr{(?!Trusted)} ] ],
+ [ "\\dL+\n", [ qr{plpgsql}, qr{Trusted} ] ],
+ # domains
+ [ "\\dD\n", [ qr{List of domains}, qr{(?!Access privileges)} ] ],
+ [ "\\dD+\n", [ qr{List of domains}, qr{Access privileges} ] ],
+ # conversions
+ [ "\\dcS\n", [ qr{List of conversions}, qr{UTF8}, qr{(?!Description)} ] ],
+ [ "\\dcS+\n", [ qr{List of conversions}, qr{LATIN1}, qr{Description} ] ],
+ # event triggers (empty)
+ [ "\\dy\n", [ qr{List of event triggers}, qr{(?!Description)} ] ],
+ [ "\\dy+\n", [ qr{List of event triggers}, qr{Description} ] ],
+ # casts
+ [ "\\dC\n", [ qr{List of casts}, qr{timestamp without time zone}, qr{(?!Description)} ] ],
+ [ "\\dC+\n", [ qr{Function}, qr{Implicit}, qr{Description} ] ],
+ # collations
+ [ "\\dOS\n", [ qr{List of collations}, qr{Deterministic}, qr{(?!Description)} ] ],
+ [ "\\dOS+\n", [ qr{POSIX}, qr{Description} ] ],
+ # schemas
+ [ "\\dn\n", [ qr{List of schemas}, qr{Owner}, qr{(?!Description)} ] ],
+ [ "\\dn+\n", [ qr{Access privileges}, qr{Description} ] ],
+ # text search misc.
+ [ "\\dFp\n", [ qr{List of text search parsers}, qr{default} ] ],
+ [ "\\dFp+\n", [ qr{hword}, qr{numword} ] ],
+ [ "\\dFd\n", [ qr{List of text search dictionaries}, qr{simple} ] ],
+ [ "\\dFd+\n", [ qr{Init options}, qr{Description} ] ],
+ [ "\\dFt\n", [ qr{List of text search templates}, qr{simple} ] ],
+ [ "\\dFt+\n", [ qr{Lexize}, qr{Description} ] ],
+ [ "\\dF\n", [ qr{List of text search configurations}, qr{simple} ] ],
+ [ "\\dF+\n", [ qr{email}, qr{url_path} ] ],
+ # extensions
+ [ "\\dx\n", [ qr{List of installed extensions}, qr{Version}, qr{plpgsql} ] ],
+ [ "\\dx+\n", [ qr{Objects in extension}, qr{plpgsql_call_handler} ] ],
+ # other backslash commands
+ [ "\\f\n", [ qr{Field separator is} ] ],
+ [ "\\H\\H\n", [ qr{Output format is html.* is aligned}s ] ],
+ [ "\\\?\n", [ qr{General}, qr{Help}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\C foobar\n", [ qr{Title is "foobar"} ] ],
+ [ "\\cd /\n\\cd\n", [ qr{^$} ] ],
+ [ "\\conninfo\n", [ qr{You are connected to database} ] ],
+ [ "\\copy (SELECT 5432 UNION SELECT 2345 ORDER BY 1) TO STDOUT\n", [ qr/\b2345\b.*\b5432\b/s ] ],
+ [ "\\copyright\n", [ qr/The Regents of the University of California/ ] ],
+ [ "\\encoding\n", [ qr/./ ] ],
+ [ "\\encoding :ENCODING\n", $EMPTY ],
+ [ "\\errverbose\n", [ qr{There is no previous error} ] ],
+ [ "\\lo_list\n", [ qr{Large objects} ] ],
+ [ "\\if true\\q\\endif\n", $EMPTY ],
+ [ "\\set\n", [ qr{ENCODING = }, qr{VERSION_NUM = } ] ],
+ [ "\\set COMP_KEYWORD_CASE preserve-lower\n\\set COMP_KEYWORD_CASE lower\n" .
+ "\\set COMP_KEYWORD_CASE upper\n\\echo :COMP_KEYWORD_CASE", [ qr/upper/ ] ],
+ [ "\\set HISTCONTROL ignorespace\n\\set HISTCONTROL ignoredups\n" .
+ "\\set HISTCONTROL ignoreboth\n\\echo :HISTCONTROL", [ qr/ignoreboth/ ] ],
+ [ "\\set ECHO_HIDDEN on\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/template0/ ] ],
+ [ "\\set ECHO_HIDDEN noexec\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/(?!template0)/ ] ],
+ [ "\\set LAST_ERROR_MESSAGE foo bla\n\\echo :LAST_ERROR_MESSAGE\n", [ qr{foobla} ] ],
+ [ "\\set AUTOCOMMIT off\n\\set ON_ERROR_ROLLBACK on\nSELECT 5432 AS pg;\nSELECT 2345 AS pg;\n",
+ [ qr{5432}, qr{2345} ] ],
+ [ "\\set ON_ERROR_ROLLBACK interactive\n\\echo :ON_ERROR_ROLLBACK\n", [ qr{interactive} ] ],
+ [ "\\set VERBOSITY verbose\n\\echo :VERBOSITY\n", [ qr{verbose} ] ],
+ [ "\\set foo bla\n\\echo :foo\\unset foo\\echo :foo\n", [ qr{^bla.*:foo}s ] ],
+ [ "\\set foo bla\n\\echo :'foo' :\"foo\"\n", [ qr{'bla'}, qr{"bla"} ] ],
+ [ "\\setenv FOO bla\n\\setenv FOO\n", [ qr{^$} ] ],
+ [ "\\timing\nSELECT 1;\n\\timing\n", [ qr{Timing is on.*Timing is off}s, qr(Time: \d+\.\d{3} ms) ] ],
+ [ "\\timing ON\n\\timing OFF\n", [ qr{Timing is on.*Timing is off}s ] ],
+ [ "\\T foo\n", [ qr{Table attributes are "foo"} ] ],
+ [ "\\T\n", [ qr{Table attributes unset} ] ],
+ # help variants
+ [ "\\?\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? commands\n",
+ [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional},
+ qr{Informational}, qr{Formatting}, qr{Connection},
+ qr{Operating System}, qr{Variables}, qr{Large Objects} ] ],
+ [ "\\? bad\n", [ qr{General} ] ],
+ [ "\\? variables\n",
+ [ qr{psql variables:}, qr{Display settings:}, qr{Environment variables} ] ],
+ [ "\\? options\n",
+ [ qr{interactive terminal}, qr{Report bugs} ] ],
+ # pset format
+ [ "\\pset format aligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is aligned}, qr{ 1 \| 2} ] ],
+ [ "\\pset format asciidoc\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is asciidoc}, qr{\|1 \|2} ] ],
+ [ "\\pset format csv\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is csv}, qr{1,2} ] ],
+ [ "\\pset format html\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is html}, qr{<td align="right">1</td>} ] ],
+ [ "\\pset format latex\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex}, qr{1 \& 2} ] ],
+ [ "\\pset format latex-longtable\n\\pset format\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is latex-longtable}, qr{\\raggedright\{1\}} ] ],
+ [ "\\pset format troff-ms\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is troff-ms}, qr{1\t2} ] ],
+ [ "\\pset format unaligned\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is unaligned}, qr{1\|2} ] ],
+ [ "\\pset format wrapped\nSELECT 1 AS one, 2 AS two;\n",
+ [ qr{Output format is wrapped}, qr{ 1 \| 2} ] ],
+ [ "\\pset format\n", [ qr{Output format is aligned} ] ],
+ # pset linestyle
+ [ "\\pset linestyle ascii\n", [ qr{Line style is ascii} ] ],
+ [ "\\pset linestyle old-ascii\n", [ qr{Line style is old-ascii} ] ],
+ [ "\\pset linestyle unicode\n", [ qr{Line style is unicode} ] ],
+ # pset unicode_*_linestyle
+ [ "\\pset unicode_header_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_header_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_border_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_border_linestyle double\n", [ qr{"double"} ] ],
+ [ "\\pset unicode_column_linestyle single\n", [ qr{"single"} ] ],
+ [ "\\pset unicode_column_linestyle double\n", [ qr{"double"} ] ],
+);
+
+for my $h (@backslash_out)
+{
+ my ($in, $out) = @$h;
+ psql('', 0, $in, $out, [ qr{^$} ], "psql -- $in");
+}
+
+# test some errors
+my @backslash_err = (
+ [ "\\unknown stuff\n", [ qr/invalid command / ] ],
+ [ "\\cd /no/such/dir\n", [ qr/could not change directory to/ ] ],
+ [ "\\copy\n", [ qr/arguments required/ ] ],
+ [ "\\copy bad stuff on the line\n", [ qr/parse error at \"/ ] ],
+ [ "\\copy binary (\n", [ qr/parse error at end of line/ ] ],
+ [ "\\copy public.regress_psql_tap_1_t1(data) to '/no/such/file'", [ qr{/no/such/file} ] ],
+ [ "\\encoding no-such-encoding\n", [ qr/invalid encoding name/ ] ],
+ [ "BAD;\n\\errverbose\n", [ qr/syntax error at or near "BAD"/, qr/\b42601\b/ ] ],
+ [ "\\dfX\n", [ qr/invalid command/ ] ],
+ [ "\\dPX\n", [ qr/invalid command/ ] ],
+ [ "\\drX\n", [ qr/invalid command/ ] ],
+ [ "\\dRX\n", [ qr/invalid command/ ] ],
+ [ "\\dFX\n", [ qr/invalid command/ ] ],
+ [ "\\deX\n", [ qr/invalid command/ ] ],
+ [ "\\dX\n", [ qr/invalid command/ ] ],
+ [ "\\i\n", [ qr/missing required argument/ ] ],
+ [ "\\ir\n", [ qr/missing required argument/] ],
+ [ "\\lo_bad\n", [ qr/invalid command/ ] ],
+ [ "\\lo_export\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_import /no/such/file\n", [ qr{no/such/file} ] ],
+ [ "\\lo_unlink\n", [ qr/missing required argument/ ] ],
+ [ "\\lo_unlink -1\n", [ qr/large object 4294967295 does not exist/ ] ],
+ [ "\\setenv\n", [ qr/missing required argument/ ] ],
+ [ "\\setenv PG_REGRESS_TEST=1\n", [ qr/environment variable name must not contain "="/ ] ],
+ [ "\\unset\n", [ qr/missing required argument/ ] ],
+ [ "\\set foo \`no-such-command\`\n", [ qr/no-such-command/ ] ],
+ [ "\\set === xxx\n", [ qr/invalid variable name/ ] ],
+ [ "\\w\n", [ qr/missing required argument/ ] ],
+ [ "\\pset format bad\n", [ qr/allowed formats are aligned, asciidoc/ ] ],
+ [ "\\pset linestyle bad\n", [ qr/allowed line styles are ascii, old-ascii, unicode/ ] ],
+ [ "\\pset unicode_border_linestyle bad\n", [ qr/allowed Unicode border line styles are single, double/ ] ],
+ [ "\\pset unicode_column_linestyle bad\n", [ qr/allowed Unicode column line styles are single, double/ ] ],
+ [ "\\pset unicode_header_linestyle bad\n", [ qr/allowed Unicode header line styles are single, double/ ] ],
+ [ "COPY regress_psql_tap_1_t1 FROM STDIN \\watch 0.01\n", [ qr/watch cannot be used with COPY/ ] ],
+ [ "\\watch 0.01\n", [ qr/watch cannot be used with an empty query/ ] ],
+ # check variable constraints values
+ [ "\\set ECHO bad\n", [ qr{none, errors, queries, all} ] ],
+ [ "\\set ECHO_HIDDEN bad\n", [ qr{on, off, noexec} ] ],
+ [ "\\set ON_ERROR_ROLLBACK bad\n", [ qr{on, off, interactive} ] ],
+ [ "\\set COMP_KEYWORD_CASE bad\n", [ qr{lower, upper, preserve-lower, preserve-upper} ] ],
+ [ "\\set HISTCONTROL bad\n", [ qr{none, ignorespace, ignoredups, ignoreboth} ] ],
+ [ "\\set VERBOSITY bad\n", [ qr{default, verbose, terse, sqlstate} ] ],
+ [ "\\set SHOW_CONTEXT bad\n", [ qr{never, errors, always} ] ],
+);
+
+for my $h (@backslash_err)
+{
+ my ($in, $err) = @$h;
+ psql('-L /dev/null', 0, $in, [ qr{^$} ], $err, "psql -- $in");
+}
+
+# other errors
+
+# \timing
+psql('', 0, "\\timing bad\n",
+ [ qr{Timing is off} ], [ qr{unrecognized value} ], 'psql \timing error');
+
+# check stdout vs stderr output
+psql('', 0, "\\echo hello\n\\warn world\n\\q\n",
+ [ qr{^hello$} ], [ qr{^world$} ], 'psql in/out/err');
+
+# \watch test
+psql('', 0,
+ "CREATE TEMP SEQUENCE tmp_seq MAXVALUE 3 NO CYCLE;\n" .
+ "\\t on\n" .
+ "\\timing\n" .
+ "SELECT 'x=' || NEXTVAL('tmp_seq') \\watch 0.01\n",
+ [ qr/x=1\b.*x=2\b.*x=3\b/s, qr/(?!x=[04-9])/ ],
+ [ qr/nextval: reached maximum value of sequence/ ],
+ 'psql watch');
+
+# guess whether it looks like unix enough for testing purposes
+my $is_like_unix = -e "/dev/null" and -d "/tmp" and -x "/bin/cat" and -x "/bin/echo";
+
+# skip un*x-specific zone if it does not look like un*x
+goto END_UNIX_ZONE
+ unless $is_like_unix;
+
+# this probably only works under UN*X-like systems
+psql('', 0, "\\setenv PSQL_EDITOR echo\nSELECT 1 AS one;\n\\e\n\\p\n",
+ [ qr/one.*one/s, qr/SELECT 1/ ], $EMPTY, 'psql edit');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ef regress_psql_tap_1_f1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ef');
+psql('', 0,
+ "\\setenv PSQL_EDITOR echo\n\\ev regress_psql_tap_1_v1\n",
+ [ qr/No changes/ ], $EMPTY, 'psql \ev');
+psql('', 0, "\\setenv FOO bla\\set foo \`echo \$FOO\`\n\\echo :foo\n",
+ [ qr/bla/ ], $EMPTY, 'psql backtick');
+psql('', 0, "\\setenv FOO blabla\n\\! echo \$FOO\n",
+ [ qr/blabla/ ], $EMPTY, 'psql !');
+
+psql('-o /dev/null', 0, "SELECT 5432 AS pg\n", $EMPTY, $EMPTY, 'psql -o null');
+psql('', 0, "\\o /dev/null\nSELECT 1;\n", $EMPTY, $EMPTY, 'psql \o null');
+psql('', 0, "\\o | cat\nSELECT 5432 AS pg;\n\\o\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql \o cat');
+
+psql('', 0, "SELECT 5432 AS pg;\n\\w /dev/null\n",
+ [ qr/pg.*\b5432\b/s ], $EMPTY, 'psql \w null');
+
+psql('', 0, "SELECT 1\\g /dev/null\n",
+ $EMPTY, $EMPTY, 'psql \g null');
+psql('', 0, "SELECT 5432\\g | cat\n",
+ [ qr/\b5432\n/ ], $EMPTY, 'psql \g cat');
+
+psql('--log-file=/dev/null', 0, "SELECT 5432 AS pg\n",
+ [ qr/\b5432\b/ ], $EMPTY, 'psql -L null');
+
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) FROM PROGRAM 'echo moe'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy echo');
+psql('', 0, "\\copy public.regress_psql_tap_1_t1(data) TO PROGRAM 'cat'\n",
+ [ qr/COPY 1\b/ ], $EMPTY, 'psql copy cat');
+
+END_UNIX_ZONE:
+
+psql('', 0, "\\i /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \i error');
+psql('', 0, "\\i ~/there/is/no/such/file\n",
+ [ qr{^$} ], [ qr{there/is/no/such/file} ], 'psql \i tilde');
+
+# no such file
+psql('', 0, "\\o /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \o error');
+
+psql('', 0, "\\w /no/such/file\n",
+ [ qr{^$} ], [ qr{/no/such/file} ], 'psql \w error');
+
+psql('-L /no/such/file', 1, '',
+ [ qr{^$} ], [ qr{could not open log file} ], 'psql -L bad');
+
+# \copy errors
+psql('', 0, "\\copy regress_psql_tap_1_t1 to program '/no/such/prgm'",
+ [ qr/COPY 1/ ], [ qr{/no/such/prgm} ], 'psql copy to bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from program '/no/such/prgm'",
+ [ qr/COPY 0/ ], [ qr{/no/such/prgm} ], 'psql copy from bad prgm');
+psql('', 0, "\\copy regress_psql_tap_1_t1 to '/tmp'",
+ $EMPTY, [ qr{directory} ], 'psql copy to tmp');
+psql('', 0, "\\copy regress_psql_tap_1_t1 from '/tmp'",
+ $EMPTY, [ qr/directory/ ], 'psql copy to tmp');
+
+# misc errors
+psql('-P format=bad', 1, '', $EMPTY,
+ [ qr/allowed formats are aligned/, qr/could not set printing parameter/ ], 'psql -P bad');
+psql('-Q', 1, '', $EMPTY, [ qr/Try ".* --help" for more information/ ], 'psql -BAD');
+
+#
+# .psqlrc
+#
+# note: reading the system wide psqlrc cannot be avoided
+#
+my $psqlrc = create_test_file('regress_psql_tap_1_psqlrc', "SELECT 1 AS one\n");
+$ENV{PSQLRC} = $psqlrc;
+psql('!', 0, "SELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], [ qr{^$} ], 'psql .psqlrc');
+delete $ENV{PSQLRC};
+ok(unlink $psqlrc, "unlink $psqlrc");
+
+#
+# large objects
+#
+my $lofile = create_test_file('regress_psql_tap_1_lo', '0123456789ABCDEF' x 1024);
+my $esc_lofile = $lofile;
+$esc_lofile =~ s/'/''/g;
+psql('-H', 0,
+ "\\lo_import '$esc_lofile' 'regress psql TAP lo'\n\\lo_list\n",
+ [ qr{lo_import \d+}, qr{Large objects}, qr{regress psql TAP lo} ], [ qr{^$} ], 'psql lo 1');
+ok(unlink $lofile, "unlink $lofile (1)");
+psql('', 0,
+ "SELECT oid AS loboid\n" .
+ "FROM pg_catalog.pg_largeobject_metadata\n" .
+ "WHERE pg_catalog.obj_description(oid, 'pg_largeobject') = 'regress psql TAP lo'\\gset\n" .
+ "\\echo loboid = :loboid\n" .
+ "\\pset tuples_only on\n" .
+ "SELECT 'lo extract: ' || lo_get(:loboid, 826, 3)::TEXT AS extract;\n" .
+ "\\lo_export :loboid '/no/such/dir/stuff'\n" . # this one fails
+ "\\lo_export :loboid '$esc_lofile'\n" .
+ "\\lo_unlink :loboid\n",
+ [ qr{loboid = \d+}, qr{lo extract: \\x414243} ],
+ [ qr{could not open file "/no/such/dir/stuff"} ],
+ 'psql lo 2');
+ok(-e $lofile, "re-created $lofile");
+ok(unlink $lofile, "unlink $lofile (2)");
+
+#
+# INTERACTIVE STUFF
+#
+# tab-completion checks are not very fast.
+# disable pager to work around pagination interactions.
+
+# basic test
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ "\\echo hello world\n", qr/hello world.*postgres=\# /s ],
+ [ "help\n", qr/You are using.*\\copyright.*quit.*postgres=\# /s ],
+ [ "SELECT\nhelp\n\\r\n", qr/help or press control-[CD].*postgres=\# /s ],
+ [ "SELECT\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT (\nquit ; \n\\r\n", qr/Use \\q to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT \$\$\nquit\n\$\$\\r\n", qr/Use control-[CD] to quit.*buffer reset.*postgres=\# /s ],
+ [ "SELECT '\nquit'\\r\n", qr/quit'.*postgres=\# /s ],
+ [ "SELECT '\n\\q\n", qr/Use control-[CD] to quit.*postgres'\# /s ],
+ [ "'\\r\n", qr/buffer reset.*postgres=\# /s ],
+ [ "SELECT 1\n\\p\n\\r\n", qr/SELECT 1.*SELECT 1.*buffer reset.*postgres=\#/s ],
+ #[ "\\prompt tmp_var\nfoobla\n\\echo hello :tmp_var\n\\unset foo\n\\echo world :tmp_var\n",
+ # qr/hello foobla.*world :tmp_var.*postgres=\# /s ],
+ [ "\\password\n", qr/Enter new password: / ],
+ [ "foo\n", qr/Enter it again: / ],
+ [ "bla\n", qr/Passwords didn't match.*postgres=\# /s ],
+ [ "exit\n" ],
+ ],
+ 'ipsql basic', 1);
+
+# prompt
+ipsql('-P pager', 0, 5,
+ [ # default prompts
+ [ "SELECT 1 +\n", qr/postgres-\# / ],
+ [ "(2 * \n", qr/postgres\(\# / ],
+ [ "3) + '\n", qr/postgres\'\# / ],
+ [ "4' + \$\$\n", qr/postgres\$\# /],
+ [ "5\$\$ AS \"\n", qr/postgres\"\# / ],
+ [ "seize\" /*\n", qr/postgres\*\# / ],
+ [ " ignored */;\n", qr/\bseize\b.*\b16\b.*postgres=\# /s ],
+ [ "\\if false\n", qr/postgres\@\# / ],
+ [ "\\echo IGNORED\n", qr/command ignored.*if block.*postgres\@\# /s ],
+ # BUG: prompt with - instead of @
+ [ "SELECT 1;\n", qr/query ignored.*if block.*postgres.\# /s ],
+ [ "\\endif\n", qr/postgres=\# / ],
+ [ "\\set SINGLELINE ON\n", qr/postgres\^\# / ],
+ [ "\\set SINGLELINE OFF\n", qr/postgres=\# / ],
+ [ "COPY regress_psql_tap_1_t1(data) FROM STDIN;\n", qr/>> / ],
+ [ "hello\nworld\n\\.\n", qr/COPY 2\b.*postgres=\# /s ],
+ # set new prompts (not tested: %`)
+ [ "\\set PROMPT1 '%%%[%]%?%M%m%>%n%/%~%#%p%R%x%l%:QUIET:%040'\n", qr/%.*\d+(on|off) /s ],
+ [ "\\set PROMPT1 '1> '\n\\set PROMPT2 '2> '\n\\set PROMPT3 '3> '\n", qr/\b1> / ],
+ [ "COPY regress_psql_tap_1_t1(data)\n", qr/\b2> / ],
+ [ "FROM STDIN;\n", qr/\b3> / ],
+ [ "calvin\nhobbes\nsusie\nmoe\n\\.\n", qr/COPY 4\b.*\b1> /s ],
+ # reset prompts
+ [ "\\set PROMPT1 '%/%R%# '\n", qr/PROMPT1.*postgres=# /s ],
+ [ "\\set PROMPT2 '%/%R%# '\n", qr/PROMPT2.*postgres=# /s ],
+ [ "\\set PROMPT3 '>> '\n", qr/PROMPT3.*postgres=# /s ],
+ ],
+ 'ipsql prompt 1');
+
+goto END_HISTORY_ZONE
+ unless $has_history;
+
+# signals
+my $stop = $is_like_unix ? "\004" : "\003";
+ipsql('-P pager', 0, 5,
+ [
+ [ "\\echo pg :VERSION_NUM\n", qr/pg \d+.*postgres=\# /s ],
+ [ $stop, qr/\\q/ ],
+ ],
+ 'ipsql signals 1', 1);
+
+psql('', 0, "\\s /dev/null\n", $EMPTY, $EMPTY, 'psql \s null');
+
+# tab-completion tests
+ipsql('-P pager', 0, 5,
+ [ # commands
+ [ "SEL\t", qr/SELECT / ],
+ [ "* FROM pg_catalog.pg_ca\t", qr/pg_catalog\.pg_cast / ],
+ [ "LIMIT 17;\n", qr/\b17 rows.*postgres=\# /s ],
+ # backslash commands
+ [ "\\d\t", qr/(\\da.*\\dy.*postgres=# )?\\d/s ],
+ [ "y\n", qr/List of event triggers.*postgres=\# /s ],
+ # pset
+ [ "\\pset \t", qr/(unicode_column_linestyle.*postgres=\# )?\\pset/s ],
+ [ "tu\t", qr/tuples_only / ],
+ [ "off\n", qr/postgres=\# / ],
+ [ "\\pset unicode_b\t", qr/unicode_border_linestyle / ],
+ [ "\t", qr/(double.*single.*postgres=\# )?/s ],
+ [ "double\n", qr/Unicode border line style is "double".*postgres=\# /s ],
+ # set
+ [ "\\set VERB\t", qr/VERBOSITY / ],
+ [ "\t", qr/(default.*verbose.*postgres=\# )?/s ],
+ [ "d\t\n", qr/default.*postgres=\# /s ],
+ # misc
+ [ "\\cd \t", qr/(.*postgres=\# )?\\cd /s ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # object type completion,
+ # strange: the list is not seen from the test driver
+ # but the coverage works as expected.
+ [ "CREATE \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "DROP \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ALTER \t\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ [ "ANALYZE (\t", qr/(SKIP_LOCKED.*postgres=\# )?ANALYZE \(/s ],
+ [ "V\tOF\t", qr/VERBOSE OFF/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ # SET
+ [ "SET \t", qr/(zero_damaged_pages.*postgres=\# )?SET /s ],
+ [ "da\t", qr/datestyle / ],
+ [ "\t", qr/TO / ],
+ [ "\tDE\t", qr/DEFAULT/ ],
+ [ ";\n", qr/SET.*postgres=\# /s ],
+ # psql variables
+ [ "\\ec\t", qr/\\echo / ],
+ [ ":q\t", qr/QUIET/ ],
+ [ ":'PO\t", qr/:'PORT'/ ],
+ [ ":\"HISTC\t", qr/:"HISTCONTROL"/ ],
+ [ "\n", qr/(on|off).*'\d+'.*"[a-z]+".*postgres=\# /s ],
+ # file
+ [ "COPY regress_psql_tap_1_t1 FROM \t", qr/(postgres=\# COPY .* FROM )?/ ],
+ [ "\\r\n", qr/Query buffer reset.*postgres=\# /s ],
+ ],
+ 'ipsql tab 1');
+
+END_HISTORY_ZONE:
+
+# final cleanup
+psql('', 0, '-- cleanup objects
+DROP VIEW regress_psql_tap_1_v1;
+DROP FUNCTION regress_psql_tap_1_f1;
+DROP TABLE regress_psql_tap_1_t1;
+DROP USER regress_psql_tap_1;
+', [ qr{DROP VIEW}, qr{DROP FUNCTION}, qr{DROP TABLE}, qr{DROP ROLE} ], [ qr{^$} ], 'psql -- drop stuff');
+
+$node->stop();
+done_testing();
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
index b685b35282..2692c99e17 100644
--- a/src/bin/scripts/t/100_vacuumdb.pl
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -92,6 +92,7 @@ $node->issues_sql_like(
$node->command_checks_all(
[ 'vacuumdb', '--analyze', '--table', 'vacview', 'postgres' ],
0,
+ '',
[qr/^.*vacuuming database "postgres"/],
[qr/^WARNING.*cannot vacuum non-tables or special system tables/s],
'vacuumdb with view');
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm
index 270bd6c856..9c28bec4a7 100644
--- a/src/test/perl/PostgresNode.pm
+++ b/src/test/perl/PostgresNode.pm
@@ -1676,6 +1676,28 @@ sub command_checks_all
=pod
+=item $node->icommand_checks(cmd, ...)
+
+TestLib::icommand_checks with our connection parameters.
+See command_ok(...)
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my $self = shift;
+
+ local $ENV{PGHOST} = $self->host;
+ local $ENV{PGPORT} = $self->port;
+
+ TestLib::icommand_checks(@_);
+ return;
+}
+
+=pod
+
=item $node->issues_sql_like(cmd, expected_sql, test_name)
Run a command on the node, then verify that $expected_sql appears in the
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 3f89d75310..0af112ca45 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -54,6 +54,7 @@ use File::stat qw(stat);
use File::Temp ();
use IPC::Run;
use SimpleTee;
+use Expect;
# specify a recent enough version of Test::More to support the
# done_testing() function
@@ -82,6 +83,7 @@ our @EXPORT = qw(
command_like_safe
command_fails_like
command_checks_all
+ icommand_checks
$windows_os
$have_io_pty
@@ -839,6 +841,8 @@ Arguments:
=item C<ret>: Expected exit code
+=item C<in>: Stdin for command
+
=item C<out>: Expected stdout from command
=item C<err>: Expected stderr from command
@@ -853,12 +857,13 @@ sub command_checks_all
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
- my ($cmd, $expected_ret, $out, $err, $test_name) = @_;
+ my ($cmd, $expected_ret, $in, $out, $err, $test_name) = @_;
+ $in = '' if not defined $in;
# run command
my ($stdout, $stderr);
print("# Running: " . join(" ", @{$cmd}) . "\n");
- IPC::Run::run($cmd, '>', \$stdout, '2>', \$stderr, @no_stdin);
+ IPC::Run::run($cmd, '<', \$in, '>', \$stdout, '2>', \$stderr);
# See http://perldoc.perl.org/perlvar.html#%24CHILD_ERROR
my $ret = $?;
@@ -887,6 +892,80 @@ sub command_checks_all
=pod
+=item icommand_checks(cmd, ret, timeout, init, inout, name)
+
+Run a command and check its status and outputs.
+Arguments:
+
+=over
+
+=item C<cmd> ref to list for command, options and arguments to run
+
+=item C<ret> expected exit status
+
+=item C<timeout> for interactions
+
+=item C<init> initial output before interacting
+
+=item C<inout> [ [ input, list of output checks ], ... ]
+
+=item C<name> of the test
+
+=back
+
+=cut
+
+sub icommand_checks
+{
+ local $Test::Builder::Level = $Test::Builder::Level + 1;
+
+ my ($cmd, $expected_ret, $timeout, $init, $inout, $name) = @_;
+
+ my $ps = Expect->spawn(@$cmd);
+ ok(defined $ps, "$name command spawn: @$cmd");
+ return unless defined $ps;
+
+ $ps->slave->stty(qw(raw));
+ my $n = 0;
+ for my $test (@$inout)
+ {
+ my ($in, @out) = @$test;
+ $n++;
+ $ps->send($in);
+ for my $o (@out)
+ {
+ my $ok = $ps->expect($timeout, '-re', $o);
+ # check header on first output
+ if (defined $init)
+ {
+ # beware of race conditions...
+ ok($ps->before =~ $init,
+ "$name header matches /$init/");
+ undef $init;
+ }
+ ok($ok, "$n: $name output matches /$o/");
+ if (not $ok)
+ {
+ print STDERR "last input: ###$in###\n";
+ print STDERR "before: ###" . $ps->before . "###\n" if $ps->before;
+ print STDERR "match: ###" . $ps->match . "###\n" if $ps->match;
+ print STDERR "after: ###" . $ps->after . "###\n" if $ps->after;
+ print STDERR "error: ###" . $ps->error . "###\n" if $ps->error;
+ }
+ }
+ }
+ $ps->soft_close();
+
+ # check status
+ my $ret = $ps->exitstatus;
+ ok($ret == $expected_ret,
+ "$name status (got $ret vs expected $expected_ret)");
+
+ return;
+}
+
+=pod
+
=back
=cut
On Wed, Nov 27, 2019 at 10:14:16AM +0100, Fabien COELHO wrote:
Indeed, I did not notice.
Thanks, Fabien!
--
Michael
Hi Fabien,
On 11/27/19 11:01 PM, Michael Paquier wrote:
On Wed, Nov 27, 2019 at 10:14:16AM +0100, Fabien COELHO wrote:
Indeed, I did not notice.
This patch no longer applies: http://cfbot.cputube.org/patch_27_2262.log
CF entry has been updated to Waiting on Author.
Regards,
--
-David
david@pgmasters.net
On 24 Mar 2020, at 15:47, David Steele <david@pgmasters.net> wrote:
This patch no longer applies: http://cfbot.cputube.org/patch_27_2262.log
CF entry has been updated to Waiting on Author.
This patch hasn't been updated and still doesn't apply, do you intend to rebase
it during this commitfest or should we move it to returned with feedback? It
can always be re-opened at a later date.
cheers ./daniel
On 5 Jul 2020, at 13:38, Daniel Gustafsson <daniel@yesql.se> wrote:
On 24 Mar 2020, at 15:47, David Steele <david@pgmasters.net> wrote:
This patch no longer applies: http://cfbot.cputube.org/patch_27_2262.log
CF entry has been updated to Waiting on Author.
This patch hasn't been updated and still doesn't apply, do you intend to rebase
it during this commitfest or should we move it to returned with feedback? It
can always be re-opened at a later date.
As the thread has stalled, I've marked this Returned with Feedback.
cheers ./daniel
Hello,
This patch no longer applies: http://cfbot.cputube.org/patch_27_2262.log
CF entry has been updated to Waiting on Author.
This patch hasn't been updated and still doesn't apply, do you intend to rebase
it during this commitfest or should we move it to returned with feedback? It
can always be re-opened at a later date.As the thread has stalled, I've marked this Returned with Feedback.
Hmmm.
AFAICR the feedback is that the Expect perl module is not welcome, which
seems to suggest that it would have to be re-implemented somehow. This is
not my dev philosophy, I won't do that, so I'm sorry to say that psql
coverage will remain pretty abysmal.
Sigh.
--
Fabien.
On 1 Aug 2020, at 09:06, Fabien COELHO <coelho@cri.ensmp.fr> wrote:
Hello,
This patch no longer applies: http://cfbot.cputube.org/patch_27_2262.log
CF entry has been updated to Waiting on Author.
This patch hasn't been updated and still doesn't apply, do you intend to rebase
it during this commitfest or should we move it to returned with feedback? It
can always be re-opened at a later date.As the thread has stalled, I've marked this Returned with Feedback.
Hmmm.
AFAICR the feedback is that the Expect perl module is not welcome, which seems to suggest that it would have to be re-implemented somehow. This is not my dev philosophy, I won't do that, so I'm sorry to say that psql coverage will remain pretty abysmal.
Re-reading this thread, I see no complaints about introducing a dependency on
Expect. The feedback returned in this case is that the patch hasn't applied
since March, and that the patch is more than welcome to be re-entered in the
next CF once it does.
cheers ./daniel
Daniel Gustafsson <daniel@yesql.se> writes:
On 1 Aug 2020, at 09:06, Fabien COELHO <coelho@cri.ensmp.fr> wrote:
AFAICR the feedback is that the Expect perl module is not welcome, which seems to suggest that it would have to be re-implemented somehow. This is not my dev philosophy, I won't do that, so I'm sorry to say that psql coverage will remain pretty abysmal.
Re-reading this thread, I see no complaints about introducing a dependency on
Expect. The feedback returned in this case is that the patch hasn't applied
since March, and that the patch is more than welcome to be re-entered in the
next CF once it does.
Personally, I'd object to introducing a hard dependency on Expect, as
there are no doubt a lot of developers and buildfarm members who don't
have that installed. But I see no reason we couldn't skip some tests
if it's lacking, as we're already doing with IO::Pty in
010_tab_completion.pl.
That does raise the question of whether Expect makes things enough
easier than raw IO::Pty that it's worth adding that dependency (and
hence foregoing the tests on some machines). But I'm prepared to be
convinced on that point.
regards, tom lane
On 8/1/20 5:27 PM, Tom Lane wrote:
Daniel Gustafsson <daniel@yesql.se> writes:
On 1 Aug 2020, at 09:06, Fabien COELHO <coelho@cri.ensmp.fr> wrote:
AFAICR the feedback is that the Expect perl module is not welcome, which seems to suggest that it would have to be re-implemented somehow. This is not my dev philosophy, I won't do that, so I'm sorry to say that psql coverage will remain pretty abysmal.
Re-reading this thread, I see no complaints about introducing a dependency on
Expect. The feedback returned in this case is that the patch hasn't applied
since March, and that the patch is more than welcome to be re-entered in the
next CF once it does.Personally, I'd object to introducing a hard dependency on Expect, as
there are no doubt a lot of developers and buildfarm members who don't
have that installed. But I see no reason we couldn't skip some tests
if it's lacking, as we're already doing with IO::Pty in
010_tab_completion.pl.That does raise the question of whether Expect makes things enough
easier than raw IO::Pty that it's worth adding that dependency (and
hence foregoing the tests on some machines). But I'm prepared to be
convinced on that point.
+1. Also note that the Windows animals don't and probably will never
support Expect, since Windows doesn't have PTYs. Expect.pm is in fact a
pure perl module that sits on top of IO::Pty, which in turn sits on top
of IO::Tty. So if you have those Expect.pm probably isn't a huge
stretch. But let's not add a dependency if we can avoid it. And if we do
add one it will need to be a soft one like the case you mentioned.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
On Sun, Aug 02, 2020 at 11:10:23AM -0400, Andrew Dunstan wrote:
+1. Also note that the Windows animals don't and probably will never
support Expect, since Windows doesn't have PTYs. Expect.pm is in fact a
pure perl module that sits on top of IO::Pty, which in turn sits on top
of IO::Tty. So if you have those Expect.pm probably isn't a huge
stretch. But let's not add a dependency if we can avoid it. And if we do
add one it will need to be a soft one like the case you mentioned.
Even with that, do we really care about some code coverage specific to
Windows for tab-complete.c? Also, how complicated does the proposed
patch become if we remove the dependency to Expect.pm and just rely on
IO::Pty?
--
Michael
Re-reading this thread, I see no complaints about introducing a
dependency on Expect.
Indeed, Tom's complaint was on another thread, possibly when interactive
tests "src/bin/psql/t/010_tab_completion.pl" were added.
ISTM that one of the issue was that some farm animal would be broken.
I'm quite lost about Expect portability discussion wrt windows, it is
unclear to me whether it is expected to work there or not.
As I stated, I do not like re-inventing the wheel, probably quite badly,
when someone else already did a good job.
--
Fabien.
On 8/3/20 3:34 PM, Fabien COELHO wrote:
I'm quite lost about Expect portability discussion wrt windows, it is
unclear to me whether it is expected to work there or not.
Sorry if I was unclear. Expect will not work on Windows. Nor will use of
IO::Pty or IO::Tty, which are what Expect uses under the hood. So use
of any of that needs to be done just as it is done on
010_tab_completion.pl, i.e.
eval { require IO::Pty; };
if ($@)
{
plan skip_all => 'IO::Pty is needed to run this test';
}
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services