tests for client programs

Started by Peter Eisentrautalmost 12 years ago20 messages
#1Peter Eisentraut
peter_e@gmx.net
1 attachment(s)

As we all know, the client programs (src/bin/) don't have any real test
suites. Some pieces are tested as part of the backend regression tests,
some as part of the pg_upgrade test script, but nothing is specifically
targeted, and pg_basebackup for example is not tested at all.

So I wrote something.

I chose to use Perl-based tools, prove and Test::More, because those are
available in a standard Perl installation, and we already require that.

I put together three handfuls of tests to show what it would look like.
For extra fun, I added a "todo" test in pg_basebackup for a feature
that's currently being proposed in the commit fest.

A significant near-future project would be adding tests for pg_dump and
pg_upgrade.

Lots of things to argue about here: tools, file layout, naming, Perl
code, test quality, etc.

To try it out, apply the attached patch and run make -C src/bin check
(or installcheck, after installation).

Attachments:

0001-Add-TAP-tests-for-client-programs.patchtext/x-patch; charset=UTF-8; name=0001-Add-TAP-tests-for-client-programs.patchDownload
>From 9805e2dc70b8c6174537423001c62c529da2c335 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Tue, 14 Jan 2014 21:51:22 -0500
Subject: [PATCH] Add TAP tests for client programs

---
 configure                                      |  47 +++++++
 configure.in                                   |   5 +
 src/Makefile.global.in                         |  17 ++-
 src/bin/initdb/.gitignore                      |   2 +
 src/bin/initdb/Makefile                        |   7 ++
 src/bin/initdb/t/001_initdb.pl                 |  37 ++++++
 src/bin/pg_basebackup/.gitignore               |   2 +
 src/bin/pg_basebackup/Makefile                 |   6 +
 src/bin/pg_basebackup/t/010_pg_basebackup.pl   |  67 ++++++++++
 src/bin/pg_basebackup/t/020_pg_receivexlog.pl  |   8 ++
 src/bin/pg_config/.gitignore                   |   1 +
 src/bin/pg_config/Makefile                     |   6 +
 src/bin/pg_config/t/001_pg_config.pl           |  12 ++
 src/bin/pg_controldata/.gitignore              |   1 +
 src/bin/pg_controldata/Makefile                |   6 +
 src/bin/pg_controldata/t/001_pg_controldata.pl |  14 +++
 src/bin/pg_ctl/.gitignore                      |   1 +
 src/bin/pg_ctl/Makefile                        |   6 +
 src/bin/pg_ctl/t/001_start_stop.pl             |  21 ++++
 src/bin/pg_ctl/t/002_status.pl                 |  14 +++
 src/bin/scripts/.gitignore                     |   2 +
 src/bin/scripts/Makefile                       |   7 ++
 src/bin/scripts/t/010_clusterdb.pl             |  18 +++
 src/bin/scripts/t/011_clusterdb_all.pl         |   9 ++
 src/bin/scripts/t/020_createdb.pl              |  16 +++
 src/bin/scripts/t/030_createlang.pl            |  18 +++
 src/bin/scripts/t/040_createuser.pl            |  26 ++++
 src/bin/scripts/t/050_dropdb.pl                |  16 +++
 src/bin/scripts/t/060_droplang.pl              |  15 +++
 src/bin/scripts/t/070_dropuser.pl              |  16 +++
 src/bin/scripts/t/080_pg_isready.pl            |  15 +++
 src/bin/scripts/t/090_reindexdb.pl             |  21 ++++
 src/bin/scripts/t/091_reindexdb_all.pl         |  11 ++
 src/bin/scripts/t/100_vacuumdb.pl              |  17 +++
 src/bin/scripts/t/101_vacuumdb_all.pl          |   9 ++
 src/test/perl/TestLib.pm                       | 164 +++++++++++++++++++++++++
 36 files changed, 659 insertions(+), 1 deletion(-)
 create mode 100644 src/bin/initdb/t/001_initdb.pl
 create mode 100644 src/bin/pg_basebackup/t/010_pg_basebackup.pl
 create mode 100644 src/bin/pg_basebackup/t/020_pg_receivexlog.pl
 create mode 100644 src/bin/pg_config/t/001_pg_config.pl
 create mode 100644 src/bin/pg_controldata/t/001_pg_controldata.pl
 create mode 100644 src/bin/pg_ctl/t/001_start_stop.pl
 create mode 100644 src/bin/pg_ctl/t/002_status.pl
 create mode 100644 src/bin/scripts/t/010_clusterdb.pl
 create mode 100644 src/bin/scripts/t/011_clusterdb_all.pl
 create mode 100644 src/bin/scripts/t/020_createdb.pl
 create mode 100644 src/bin/scripts/t/030_createlang.pl
 create mode 100644 src/bin/scripts/t/040_createuser.pl
 create mode 100644 src/bin/scripts/t/050_dropdb.pl
 create mode 100644 src/bin/scripts/t/060_droplang.pl
 create mode 100644 src/bin/scripts/t/070_dropuser.pl
 create mode 100644 src/bin/scripts/t/080_pg_isready.pl
 create mode 100644 src/bin/scripts/t/090_reindexdb.pl
 create mode 100644 src/bin/scripts/t/091_reindexdb_all.pl
 create mode 100644 src/bin/scripts/t/100_vacuumdb.pl
 create mode 100644 src/bin/scripts/t/101_vacuumdb_all.pl
 create mode 100644 src/test/perl/TestLib.pm

diff --git a/configure b/configure
index 8760643..9a9c729 100755
--- a/configure
+++ b/configure
@@ -627,6 +627,7 @@ ac_includes_default="\
 
 ac_subst_vars='LTLIBOBJS
 vpath_build
+PROVE
 OSX
 XSLTPROC
 COLLATEINDEX
@@ -14614,6 +14615,52 @@ fi
 done
 
 
+#
+# Check for test tools
+#
+for ac_prog in prove
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_PROVE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$PROVE"; then
+  ac_cv_prog_PROVE="$PROVE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_PROVE="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+PROVE=$ac_cv_prog_PROVE
+if test -n "$PROVE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROVE" >&5
+$as_echo "$PROVE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$PROVE" && break
+done
+
+
 # Thread testing
 
 # We have to run the thread test near the end so we have all our symbols
diff --git a/configure.in b/configure.in
index 63c8d42..dea2ecc 100644
--- a/configure.in
+++ b/configure.in
@@ -1859,6 +1859,11 @@ PGAC_PATH_COLLATEINDEX
 AC_CHECK_PROGS(XSLTPROC, xsltproc)
 AC_CHECK_PROGS(OSX, [osx sgml2xml sx])
 
+#
+# Check for test tools
+#
+AC_CHECK_PROGS(PROVE, prove)
+
 # Thread testing
 
 # We have to run the thread test near the end so we have all our symbols
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index e0e9b79..4c862ae 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -1,5 +1,6 @@
 # -*-makefile-*-
 # src/Makefile.global.in
+# @configure_input@
 
 #------------------------------------------------------------------------------
 # All PostgreSQL makefiles include this file and use the variables it sets,
@@ -292,6 +293,20 @@ XGETTEXT = @XGETTEXT@
 GZIP	= gzip
 BZIP2	= bzip2
 
+PROVE = @PROVE@
+PG_PROVE_FLAGS = --ext='.pl' -I $(top_srcdir)/src/test/perl/
+PROVE_FLAGS = --verbose
+
+define prove_installcheck
+PATH="$(bindir):$$PATH" $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
+endef
+
+define prove_check
+$(MKDIR_P) tmp_check/log
+$(MAKE) -C $(top_builddir) DESTDIR=$(CURDIR)/tmp_check/install install >$(CURDIR)/tmp_check/log/install.log 2>&1
+PATH="$(CURDIR)/tmp_check/install$(bindir):$$PATH" $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
+endef
+
 # Installation.
 
 install_bin = @install_bin@
@@ -755,7 +770,7 @@ gcda_files := $(wildcard *.gcda)
 
 lcov.info: $(gcda_files)
 	rm -f *.gcov
-	$(if $^,$(LCOV) -d . -c -o $@ $(LCOVFLAGS))
+	$(if $^,$(LCOV) -d . -c -o $@ $(LCOVFLAGS) --gcov-tool $(GCOV))
 
 %.c.gcov: %.gcda | lcov.info
 	$(GCOV) -b -f -p -o . $(GCOVFLAGS) $*.c >$*.c.gcov.out
diff --git a/src/bin/initdb/.gitignore b/src/bin/initdb/.gitignore
index 0f74727..71a899f 100644
--- a/src/bin/initdb/.gitignore
+++ b/src/bin/initdb/.gitignore
@@ -2,3 +2,5 @@
 /localtime.c
 
 /initdb
+
+/tmp_check/
diff --git a/src/bin/initdb/Makefile b/src/bin/initdb/Makefile
index 1ece7ac..fb7d142 100644
--- a/src/bin/initdb/Makefile
+++ b/src/bin/initdb/Makefile
@@ -57,3 +57,10 @@ clean distclean maintainer-clean:
 
 # ensure that changes in datadir propagate into object file
 initdb.o: initdb.c $(top_builddir)/src/Makefile.global
+
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
new file mode 100644
index 0000000..ad6d74c
--- /dev/null
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -0,0 +1,37 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 14;
+
+my $tempdir = TestLib::tempdir;
+
+program_help_ok('initdb');
+program_version_ok('initdb');
+program_options_handling_ok('initdb');
+
+command_ok(['initdb', "$tempdir/data"], 'basic initdb');
+command_fails(['initdb', "$tempdir/data"], 'existing data directory');
+command_ok(['initdb', '-N', "$tempdir/data2"], 'nosync');
+command_ok(['initdb', '-S', "$tempdir/data2"], 'sync only');
+command_fails(['initdb', '-S', "$tempdir/data3"], 'sync missing data directory');
+mkdir "$tempdir/data4" or BAIL_OUT($!);
+command_ok(['initdb', "$tempdir/data4"], 'existing empty data directory');
+
+system_or_bail "rm -rf $tempdir/*";
+
+command_ok(['initdb', "$tempdir/data", '-X', "$tempdir/pgxlog"], 'separate xlog directory');
+
+system_or_bail "rm -rf $tempdir/*";
+command_fails(['initdb', "$tempdir/data", '-X', 'pgxlog'], 'relative xlog directory not allowed');
+
+system_or_bail "rm -rf $tempdir/*";
+mkdir "$tempdir/pgxlog";
+command_ok(['initdb', "$tempdir/data", '-X', "$tempdir/pgxlog"], 'existing empty xlog directory');
+
+system_or_bail "rm -rf $tempdir/*";
+mkdir "$tempdir/pgxlog";
+mkdir "$tempdir/pgxlog/lost+found";
+command_fails(['initdb', "$tempdir/data", '-X', "$tempdir/pgxlog"], 'existing nonempty xlog directory');
+
+system_or_bail "rm -rf $tempdir/*";
+command_ok(['initdb', "$tempdir/data", '-T', 'german'], 'select default dictionary');
diff --git a/src/bin/pg_basebackup/.gitignore b/src/bin/pg_basebackup/.gitignore
index 1334a1f..17c1731 100644
--- a/src/bin/pg_basebackup/.gitignore
+++ b/src/bin/pg_basebackup/.gitignore
@@ -1,2 +1,4 @@
 /pg_basebackup
 /pg_receivexlog
+
+/tmp_check/
diff --git a/src/bin/pg_basebackup/Makefile b/src/bin/pg_basebackup/Makefile
index 17c91af..1b8d411 100644
--- a/src/bin/pg_basebackup/Makefile
+++ b/src/bin/pg_basebackup/Makefile
@@ -41,3 +41,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_basebackup$(X) pg_receivexlog$(X) $(OBJS) pg_basebackup.o pg_receivexlog.o
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
new file mode 100644
index 0000000..b4c0471
--- /dev/null
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -0,0 +1,67 @@
+use strict;
+use warnings;
+use Cwd;
+use TestLib;
+use Test::More tests => 19;
+
+program_help_ok('pg_basebackup');
+program_version_ok('pg_basebackup');
+program_options_handling_ok('pg_basebackup');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+command_fails(['pg_basebackup'], 'pg_basebackup needs target directory specified');
+command_fails(['pg_basebackup', '-D', "$tempdir/backup"], 'pg_basebackup fails because of hba');
+
+open HBA, ">>$tempdir/pgdata/pg_hba.conf";
+print HBA "local replication all trust\n";
+print HBA "host replication all 127.0.0.1/32 trust\n";
+print HBA "host replication all ::1/128 trust\n";
+close HBA;
+system_or_bail 'pg_ctl', '-s', '-D', "$tempdir/pgdata", 'reload';
+
+command_fails(['pg_basebackup', '-D', "$tempdir/backup"], 'pg_basebackup fails because of WAL configuration');
+
+open CONF, ">>$tempdir/pgdata/postgresql.conf";
+print CONF "max_wal_senders = 10\n";
+print CONF "wal_level = archive\n";
+close CONF;
+restart_test_server;
+
+command_ok(['pg_basebackup', '-D', "$tempdir/backup"], 'pg_basebackup runs');
+ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+
+command_ok(['pg_basebackup', '-D', "$tempdir/backup2", '--xlogdir', "$tempdir/xlog2"], 'separate xlog directory');
+ok(-f "$tempdir/backup2/PG_VERSION", 'backup was created');
+ok(-d "$tempdir/xlog2/", 'xlog directory was created');
+
+command_ok(['pg_basebackup', '-D', "$tempdir/tarbackup", '-Ft'], 'tar format');
+ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
+
+mkdir "$tempdir/tblspc1";
+psql 'postgres', "CREATE TABLESPACE tblspc1 LOCATION '$tempdir/tblspc1';";
+psql 'postgres', "CREATE TABLE test1 (a int) TABLESPACE tblspc1;";
+command_ok(['pg_basebackup', '-D', "$tempdir/tarbackup2", '-Ft'], 'tar format with tablespaces');
+ok(-f "$tempdir/tarbackup2/base.tar", 'backup tar was created');
+my @tblspc_tars = glob "$tempdir/tarbackup2/[0-9]*.tar";
+note("tablespace tars are @tblspc_tars");
+is(scalar(@tblspc_tars), 1, 'one tablespace tar was created');
+
+
+our $TODO = 'https://commitfest.postgresql.org/action/patch_view?id=1303';
+
+command_fails(['pg_basebackup', '-D', "$tempdir/same", '--xlogdir', "$tempdir/same"],
+			  'fails if data and xlog directory are the same');
+
+my $pwd = cwd();
+chdir $tempdir or BAIL_OUT("could not chdir to $tempdir: $!");
+
+command_fails(['pg_basebackup', '-D', "$tempdir/same2", '--xlogdir', "same2"],
+			  'fails if data and xlog directory are the same, relative xlog directory');
+command_fails(['pg_basebackup', '-D', "same3", '--xlogdir', "$tempdir/same3"],
+			  'fails if data and xlog directory are the same, relative data directory');
+
+chdir $pwd or BAIL_OUT("could not chdir to $pwd: $!");;
+
+$TODO = undef;
diff --git a/src/bin/pg_basebackup/t/020_pg_receivexlog.pl b/src/bin/pg_basebackup/t/020_pg_receivexlog.pl
new file mode 100644
index 0000000..700ae04
--- /dev/null
+++ b/src/bin/pg_basebackup/t/020_pg_receivexlog.pl
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 3;
+
+program_help_ok('pg_receivexlog');
+program_version_ok('pg_receivexlog');
+program_options_handling_ok('pg_receivexlog');
diff --git a/src/bin/pg_config/.gitignore b/src/bin/pg_config/.gitignore
index 169bc76..cc42247 100644
--- a/src/bin/pg_config/.gitignore
+++ b/src/bin/pg_config/.gitignore
@@ -1 +1,2 @@
 /pg_config
+/tmp_check/
diff --git a/src/bin/pg_config/Makefile b/src/bin/pg_config/Makefile
index 578a2a5..579dce6 100644
--- a/src/bin/pg_config/Makefile
+++ b/src/bin/pg_config/Makefile
@@ -47,3 +47,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_config$(X) $(OBJS)
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_config/t/001_pg_config.pl b/src/bin/pg_config/t/001_pg_config.pl
new file mode 100644
index 0000000..200f394
--- /dev/null
+++ b/src/bin/pg_config/t/001_pg_config.pl
@@ -0,0 +1,12 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 7;
+
+program_help_ok('pg_config');
+program_version_ok('pg_config');
+program_options_handling_ok('pg_config');
+command_like(['pg_config', '--bindir'], qr/bin/, 'pg_config single option');  # XXX might be wrong
+command_like(['pg_config', '--bindir', '--libdir'],  qr/bin.*\n.*lib/, 'pg_config two options');
+command_like(['pg_config', '--libdir', '--bindir'],  qr/lib.*\n.*bin/, 'pg_config two options different order');
+command_like(['pg_config'], qr/.*\n.*\n.*/, 'pg_config without options prints many lines');
diff --git a/src/bin/pg_controldata/.gitignore b/src/bin/pg_controldata/.gitignore
index eab0c28..051d71d 100644
--- a/src/bin/pg_controldata/.gitignore
+++ b/src/bin/pg_controldata/.gitignore
@@ -1 +1,2 @@
 /pg_controldata
+/tmp_check/
diff --git a/src/bin/pg_controldata/Makefile b/src/bin/pg_controldata/Makefile
index 8b5f340..5abe76c 100644
--- a/src/bin/pg_controldata/Makefile
+++ b/src/bin/pg_controldata/Makefile
@@ -33,3 +33,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_controldata$(X) $(OBJS)
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
new file mode 100644
index 0000000..3ddab80
--- /dev/null
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -0,0 +1,14 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+my $tempdir = TestLib::tempdir;
+
+program_help_ok('pg_controldata');
+program_version_ok('pg_controldata');
+program_options_handling_ok('pg_controldata');
+command_fails(['pg_controldata'], 'pg_controldata without arguments fails');
+command_fails(['pg_controldata', 'nonexistent'], 'pg_controldata with nonexistent directory fails');
+system_or_bail "initdb -D $tempdir/data -A trust";
+command_like(['pg_controldata', "$tempdir/data"], qr/checkpoint/, 'pg_controldata produces output');
diff --git a/src/bin/pg_ctl/.gitignore b/src/bin/pg_ctl/.gitignore
index c90c103..73ab4ed 100644
--- a/src/bin/pg_ctl/.gitignore
+++ b/src/bin/pg_ctl/.gitignore
@@ -1 +1,2 @@
 /pg_ctl
+/tmp_check/
diff --git a/src/bin/pg_ctl/Makefile b/src/bin/pg_ctl/Makefile
index cbc1638..a9a0366 100644
--- a/src/bin/pg_ctl/Makefile
+++ b/src/bin/pg_ctl/Makefile
@@ -36,3 +36,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_ctl$(X) $(OBJS)
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
new file mode 100644
index 0000000..6818ba4
--- /dev/null
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 10;
+
+my $tempdir = TestLib::tempdir;
+
+program_help_ok('pg_ctl');
+program_version_ok('pg_ctl');
+program_options_handling_ok('pg_ctl');
+
+command_ok(['pg_ctl', 'initdb', '-D', "$tempdir/data"], 'pg_ctl initdb');
+command_ok(['pg_ctl', 'start', '-D', "$tempdir/data", '-w'], 'pg_ctl start -w');
+command_ok(['pg_ctl', 'start', '-D', "$tempdir/data", '-w'], 'second pg_ctl start succeeds');
+command_ok(['pg_ctl', 'stop', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'pg_ctl stop -w');
+command_fails(['pg_ctl', 'stop', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'second pg_ctl stop fails');
+
+command_ok(['pg_ctl', 'restart', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'pg_ctl restart with server not running');
+command_ok(['pg_ctl', 'restart', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'pg_ctl restart with server running');
+
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data", '-m', 'fast';
diff --git a/src/bin/pg_ctl/t/002_status.pl b/src/bin/pg_ctl/t/002_status.pl
new file mode 100644
index 0000000..53524eb
--- /dev/null
+++ b/src/bin/pg_ctl/t/002_status.pl
@@ -0,0 +1,14 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 2;
+
+my $tempdir = TestLib::tempdir;
+
+system_or_bail "initdb -D $tempdir/data -A trust";
+command_exit_is(['pg_ctl', 'status', '-D', "$tempdir/data"], 3, 'pg_ctl status with server not running');
+
+system_or_bail 'pg_ctl', '-s', '-l', "$tempdir/logfile", '-D', "$tempdir/data", '-w', 'start';
+command_exit_is(['pg_ctl', 'status', '-D', "$tempdir/data"], 0, 'pg_ctl status with server running');
+
+system_or_bail 'pg_ctl', '-s', 'stop', '-D', "$tempdir/data", '-m', 'fast';
diff --git a/src/bin/scripts/.gitignore b/src/bin/scripts/.gitignore
index 0b9b786..1056b28 100644
--- a/src/bin/scripts/.gitignore
+++ b/src/bin/scripts/.gitignore
@@ -14,3 +14,5 @@
 /kwlookup.c
 /mbprint.c
 /print.c
+
+/tmp_check/
diff --git a/src/bin/scripts/Makefile b/src/bin/scripts/Makefile
index b5d9207..de0e11f 100644
--- a/src/bin/scripts/Makefile
+++ b/src/bin/scripts/Makefile
@@ -68,3 +68,10 @@ clean distclean maintainer-clean:
 	rm -f $(addsuffix $(X), $(PROGRAMS)) $(addsuffix .o, $(PROGRAMS))
 	rm -f common.o dumputils.o kwlookup.o keywords.o print.o mbprint.o $(WIN32RES)
 	rm -f dumputils.c print.c mbprint.c kwlookup.c keywords.c
+
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/scripts/t/010_clusterdb.pl b/src/bin/scripts/t/010_clusterdb.pl
new file mode 100644
index 0000000..371b2dd
--- /dev/null
+++ b/src/bin/scripts/t/010_clusterdb.pl
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+program_help_ok('clusterdb');
+program_version_ok('clusterdb');
+program_options_handling_ok('clusterdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['clusterdb', 'postgres'], qr/statement: CLUSTER;/, 'SQL CLUSTER run');
+
+command_fails(['clusterdb', '-t', 'nonexistent', 'postgres'], 'fails with nonexistent table');
+
+psql 'postgres', 'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a); CLUSTER test1 USING test1x';
+issues_sql_like(['clusterdb', 'postgres', '-t', 'test1'], qr/statement: CLUSTER test1;/, 'cluster specific table');
diff --git a/src/bin/scripts/t/011_clusterdb_all.pl b/src/bin/scripts/t/011_clusterdb_all.pl
new file mode 100644
index 0000000..304c4be
--- /dev/null
+++ b/src/bin/scripts/t/011_clusterdb_all.pl
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 1;
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['clusterdb', '-a'], qr/statement: CLUSTER.*statement: CLUSTER/s, 'cluster all databases');
diff --git a/src/bin/scripts/t/020_createdb.pl b/src/bin/scripts/t/020_createdb.pl
new file mode 100644
index 0000000..8b82a2b
--- /dev/null
+++ b/src/bin/scripts/t/020_createdb.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+program_help_ok('createdb');
+program_version_ok('createdb');
+program_options_handling_ok('createdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['createdb', 'foobar1'], qr/statement: CREATE DATABASE foobar1/, 'SQL CREATE DATABASE run');
+issues_sql_like(['createdb', 'foobar2', '-l', 'C', '-E', 'LATIN1', '-T', 'template0'], qr/statement: CREATE DATABASE foobar2 ENCODING 'LATIN1'/, 'create database with encoding');
+
+command_fails(['createdb', 'foobar1'], 'fails if database already exists');
diff --git a/src/bin/scripts/t/030_createlang.pl b/src/bin/scripts/t/030_createlang.pl
new file mode 100644
index 0000000..9a87f4c
--- /dev/null
+++ b/src/bin/scripts/t/030_createlang.pl
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+program_help_ok('createlang');
+program_version_ok('createlang');
+program_options_handling_ok('createlang');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+command_fails(['createlang', 'plpgsql', 'postgres'], 'fails if language already exists');
+
+psql 'postgres', 'DROP EXTENSION plpgsql';
+issues_sql_like(['createlang', 'plpgsql', 'postgres'], qr/statement: CREATE EXTENSION "plpgsql"/, 'SQL CREATE EXTENSION run');
+
+command_like(['createlang', '--list', 'postgres'], qr/plpgsql/, 'list output');
diff --git a/src/bin/scripts/t/040_createuser.pl b/src/bin/scripts/t/040_createuser.pl
new file mode 100644
index 0000000..922873a
--- /dev/null
+++ b/src/bin/scripts/t/040_createuser.pl
@@ -0,0 +1,26 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 8;
+
+program_help_ok('createuser');
+program_version_ok('createuser');
+program_options_handling_ok('createuser');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['createuser', 'user1'],
+				qr/statement: CREATE ROLE user1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN;/,
+				'SQL CREATE USER run');
+issues_sql_like(['createuser', '-L', 'role1'],
+				qr/statement: CREATE ROLE role1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN;/,
+				'create a non-login role');
+issues_sql_like(['createuser', '-r', 'user2'],
+				qr/statement: CREATE ROLE user2 NOSUPERUSER NOCREATEDB CREATEROLE INHERIT LOGIN;/,
+				'create a CREATEROLE user');
+issues_sql_like(['createuser', '-s', 'user3'],
+				qr/statement: CREATE ROLE user3 SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;/,
+				'create a superuser');
+
+command_fails(['createuser', 'user1'], 'fails if role already exists');
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
new file mode 100644
index 0000000..3662dd0
--- /dev/null
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('dropdb');
+program_version_ok('dropdb');
+program_options_handling_ok('dropdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+psql 'postgres', 'CREATE DATABASE foobar1';
+issues_sql_like(['dropdb', 'foobar1'], qr/statement: DROP DATABASE foobar1/, 'SQL DROP DATABASE run');
+
+command_fails(['dropdb', 'nonexistent'], 'fails with nonexistent database');
diff --git a/src/bin/scripts/t/060_droplang.pl b/src/bin/scripts/t/060_droplang.pl
new file mode 100644
index 0000000..47cb48f
--- /dev/null
+++ b/src/bin/scripts/t/060_droplang.pl
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('droplang');
+program_version_ok('droplang');
+program_options_handling_ok('droplang');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['droplang', 'plpgsql', 'postgres'], qr/statement: DROP EXTENSION "plpgsql"/, 'SQL DROP EXTENSION run');
+
+command_fails(['droplang', 'nonexistent', 'postgres'], 'fails with nonexistent language');
diff --git a/src/bin/scripts/t/070_dropuser.pl b/src/bin/scripts/t/070_dropuser.pl
new file mode 100644
index 0000000..495636a
--- /dev/null
+++ b/src/bin/scripts/t/070_dropuser.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('dropuser');
+program_version_ok('dropuser');
+program_options_handling_ok('dropuser');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+psql 'postgres', 'CREATE ROLE foobar1';
+issues_sql_like(['dropuser', 'foobar1'], qr/statement: DROP ROLE foobar1/, 'SQL DROP ROLE run');
+
+command_fails(['dropuser', 'nonexistent'], 'fails with nonexistent user');
diff --git a/src/bin/scripts/t/080_pg_isready.pl b/src/bin/scripts/t/080_pg_isready.pl
new file mode 100644
index 0000000..03c3657
--- /dev/null
+++ b/src/bin/scripts/t/080_pg_isready.pl
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('pg_isready');
+program_version_ok('pg_isready');
+program_options_handling_ok('pg_isready');
+
+command_fails(['pg_isready'], 'fails with no server running');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+command_ok(['pg_isready'], 'succeeds with server running');
diff --git a/src/bin/scripts/t/090_reindexdb.pl b/src/bin/scripts/t/090_reindexdb.pl
new file mode 100644
index 0000000..18756e8
--- /dev/null
+++ b/src/bin/scripts/t/090_reindexdb.pl
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 7;
+
+program_help_ok('reindexdb');
+program_version_ok('reindexdb');
+program_options_handling_ok('reindexdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+$ENV{PGOPTIONS} = '--client-min-messages=WARNING';
+
+issues_sql_like(['reindexdb', 'postgres'], qr/statement: REINDEX DATABASE postgres;/, 'SQL REINDEX run');
+
+psql 'postgres', 'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a);';
+issues_sql_like(['reindexdb', 'postgres', '-t', 'test1'], qr/statement: REINDEX TABLE test1;/, 'reindex specific table');
+issues_sql_like(['reindexdb', 'postgres', '-i', 'test1x'], qr/statement: REINDEX INDEX test1x;/, 'reindex specific index');
+
+issues_sql_like(['reindexdb', 'postgres', '-s'], qr/statement: REINDEX SYSTEM postgres;/, 'reindex system tables');
diff --git a/src/bin/scripts/t/091_reindexdb_all.pl b/src/bin/scripts/t/091_reindexdb_all.pl
new file mode 100644
index 0000000..eee8ba8
--- /dev/null
+++ b/src/bin/scripts/t/091_reindexdb_all.pl
@@ -0,0 +1,11 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 1;
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+$ENV{PGOPTIONS} = '--client-min-messages=WARNING';
+
+issues_sql_like(['reindexdb', '-a'], qr/statement: REINDEX.*statement: REINDEX/s, 'reindex all databases');
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
new file mode 100644
index 0000000..39f1cec
--- /dev/null
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 8;
+
+program_help_ok('vacuumdb');
+program_version_ok('vacuumdb');
+program_options_handling_ok('vacuumdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['vacuumdb', 'postgres'], qr/statement: VACUUM;/, 'SQL VACUUM run');
+issues_sql_like(['vacuumdb', '-f', 'postgres'], qr/statement: VACUUM \(FULL\);/, 'vacuumdb -f');
+issues_sql_like(['vacuumdb', '-F', 'postgres'], qr/statement: VACUUM \(FREEZE\);/, 'vacuumdb -F');
+issues_sql_like(['vacuumdb', '-z', 'postgres'], qr/statement: VACUUM \(ANALYZE\);/, 'vacuumdb -z');
+issues_sql_like(['vacuumdb', '-Z', 'postgres'], qr/statement: ANALYZE;/, 'vacuumdb -z');
diff --git a/src/bin/scripts/t/101_vacuumdb_all.pl b/src/bin/scripts/t/101_vacuumdb_all.pl
new file mode 100644
index 0000000..b5779bc
--- /dev/null
+++ b/src/bin/scripts/t/101_vacuumdb_all.pl
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 1;
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['vacuumdb', '-a'], qr/statement: VACUUM.*statement: VACUUM/s, 'vacuum all databases');
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
new file mode 100644
index 0000000..2768f67
--- /dev/null
+++ b/src/test/perl/TestLib.pm
@@ -0,0 +1,164 @@
+package TestLib;
+
+use strict;
+use warnings;
+
+use Exporter 'import';
+our @EXPORT = qw(
+	tempdir
+	start_test_server
+	restart_test_server
+	psql
+	system_or_bail
+
+	command_ok
+	command_fails
+	command_exit_is
+	program_help_ok
+	program_version_ok
+	program_options_handling_ok
+	command_like
+	issues_sql_like
+);
+
+use Cwd;
+use File::Spec;
+use File::Temp ();
+use IPC::Run qw(run start);
+use Test::More;
+
+
+#
+# Helper functions
+#
+
+
+sub tempdir {
+	return File::Temp::tempdir('testXXXX', DIR => cwd(), CLEANUP => 1);
+}
+
+my ($test_server_datadir, $test_server_logfile);
+
+sub start_test_server {
+	my ($tempdir) = @_;
+	my $ret;
+
+	system "initdb -D $tempdir/pgdata -A trust -N >/dev/null";
+	$ret = system 'pg_ctl', '-D', "$tempdir/pgdata", '-s', '-w', '-l', "$tempdir/logfile", '-o', "--fsync=off -k $tempdir -p 65432 --log-statement=all", 'start';
+
+	if ($ret != 0) {
+		system('cat', "$tempdir/logfile");
+		BAIL_OUT("pg_ctl failed");
+	}
+
+	$ENV{PGHOST} = $tempdir;
+	$ENV{PGPORT} = 65432;
+	$test_server_datadir = "$tempdir/pgdata";
+	$test_server_logfile = "$tempdir/logfile";
+}
+
+sub restart_test_server {
+	system 'pg_ctl', '-s', '-D', $test_server_datadir, '-w', '-l', $test_server_logfile, 'restart';
+}
+
+END {
+	if ($test_server_datadir) {
+		system 'pg_ctl', '-D', $test_server_datadir, '-s', '-w', '-m', 'immediate', 'stop';
+	}
+}
+
+sub psql {
+	my ($dbname, $sql) = @_;
+	run ['psql', '-X', '-q', '-d', $dbname, '-f', '-'], '<', \$sql or die;
+}
+
+sub system_or_bail {
+	system(@_) == 0 or BAIL_OUT("system @_ failed: $?");
+}
+
+
+#
+# Test functions
+#
+
+
+sub command_ok {
+	my ($cmd, $test_name) = @_;
+	#my $result = run $cmd, '>', File::Spec->devnull(), '2>', File::Spec->devnull();
+	my $result = run $cmd;
+	ok($result, $test_name);
+}
+
+sub command_fails {
+	my ($cmd, $test_name) = @_;
+	my $result = run $cmd, '>', File::Spec->devnull(), '2>', File::Spec->devnull();
+	ok(!$result, $test_name);
+}
+
+sub command_exit_is {
+	my ($cmd, $expected, $test_name) = @_;
+	my $h = start $cmd, '>', File::Spec->devnull(), '2>', File::Spec->devnull();
+	$h->finish();
+	is($h->result(0), $expected, $test_name);
+}
+
+sub program_help_ok {
+	my ($cmd) = @_;
+	subtest "$cmd --help" => sub {
+		plan tests => 3;
+		my ($stdout, $stderr);
+		my $result = run [$cmd, '--help'], '>', \$stdout, '2>', \$stderr;
+		ok($result, "$cmd --help exit code 0");
+		isnt($stdout, '', "$cmd --help goes to stdout");
+		is($stderr, '', "$cmd --help nothing to stderr");
+	};
+}
+
+sub program_version_ok {
+	my ($cmd) = @_;
+	subtest "$cmd --version" => sub {
+		plan tests => 3;
+		my ($stdout, $stderr);
+		my $result = run [$cmd, '--version'], '>', \$stdout, '2>', \$stderr;
+		ok($result, "$cmd --version exit code 0");
+		isnt($stdout, '', "$cmd --version goes to stdout");
+		is($stderr, '', "$cmd --version nothing to stderr");
+	};
+}
+
+sub program_options_handling_ok {
+	my ($cmd) = @_;
+	subtest "$cmd options handling" => sub {
+		plan tests => 2;
+		my ($stdout, $stderr);
+		my $result = run [$cmd, '--not-a-valid-option'], '>', \$stdout, '2>', \$stderr;
+		ok(!$result, "$cmd with invalid option nonzero exit code");
+		isnt($stderr, '', "$cmd with invalid option prints error message");
+	};
+}
+
+sub command_like {
+	my ($cmd, $expected_stdout, $test_name) = @_;
+	subtest $test_name => sub {
+		plan tests => 3;
+		my ($stdout, $stderr);
+		my $result = run $cmd, '>', \$stdout, '2>', \$stderr;
+		ok($result, "@$cmd exit code 0");
+		is($stderr, '', "@$cmd no stderr");
+		like($stdout, $expected_stdout, "$test_name: matches");
+	};
+}
+
+sub issues_sql_like {
+	my ($cmd, $expected_sql, $test_name) = @_;
+	subtest $test_name => sub {
+		plan tests => 2;
+		truncate $test_server_logfile, 0;
+		my $result = run $cmd;
+		ok($result, "@$cmd exit code 0");
+		my $log = `cat $test_server_logfile`;
+		like($log, $expected_sql, "$test_name: SQL found in server log");
+	};
+}
+
+1;
-- 
1.8.5.1

#2Erik Rijkers
er@xs4all.nl
In reply to: Peter Eisentraut (#1)
Re: tests for client programs

On Wed, January 15, 2014 06:30, Peter Eisentraut wrote:

As we all know, the client programs (src/bin/) don't have any real test
suites. Some pieces are tested as part of the backend regression tests,
some as part of the pg_upgrade test script, but nothing is specifically
targeted, and pg_basebackup for example is not tested at all.

So I wrote something.

I chose to use Perl-based tools, prove and Test::More, because those are
available in a standard Perl installation, and we already require that.

[ 0001-Add-TAP-tests-for-client-programs.patch ]

With perl 5.18.2, in centos 6.5; system provided perl 5.10 has the same problem.

The seems to be a dependency on IPC::Run

I can install that, of course... but I suppose you want to make this work without that.

Thanks,

Erik Rijkers

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Peter Eisentraut
peter_e@gmx.net
In reply to: Erik Rijkers (#2)
Re: tests for client programs

On 1/15/14, 1:46 AM, Erik Rijkers wrote:

The seems to be a dependency on IPC::Run

I can install that, of course... but I suppose you want to make this work without that.

No, IPC::Run will be required. It looked like it was part of the
default installation where I tested, but apparently not.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Erik Rijkers
er@xs4all.nl
In reply to: Peter Eisentraut (#1)
1 attachment(s)
Re: tests for client programs

On Wed, January 15, 2014 06:30, Peter Eisentraut wrote:

As we all know, the client programs (src/bin/) don't have any real test

So I wrote something.

I chose to use Perl-based tools, prove and Test::More, because those are

[ 0001-Add-TAP-tests-for-client-programs.patch ] 32 k

I gave this a quick try.

Centos 6.5 final / perl 5.18.2

As mentioned earlier I had to install IPC::Run.

2 tests stumbled:

1. One test ( pg_ctl/t/001_start_stop.pl ) failed because I had PGDATA set. I unset all PG+ vars after that. No a big
problem but nonetheless it might be better if the test suite removes /controls the variables before running.

2. The pg_isready test failed command_fails() ('fails with no server running') because it defaults to the compiled-in
server-port (and that server was running). I added the test-designated port (65432, as defined in TestLib.pm). This
simple change is in the attached patch.

With these two changes the whole test suite passed.

Thanks,

Erik Rijkers

Attachments:

080_pg_isready.pl.difftext/x-patch; name=080_pg_isready.pl.diffDownload
--- src/bin/scripts/t/080_pg_isready.pl.orig	2014-01-15 20:08:16.325916223 +0100
+++ src/bin/scripts/t/080_pg_isready.pl	2014-01-15 20:18:24.705927054 +0100
@@ -7,7 +7,7 @@
 program_version_ok('pg_isready');
 program_options_handling_ok('pg_isready');
 
-command_fails(['pg_isready'], 'fails with no server running');
+command_fails(['pg_isready', '-p65432'], 'fails with no server running');
 
 my $tempdir = tempdir;
 start_test_server $tempdir;
#5Peter Eisentraut
peter_e@gmx.net
In reply to: Erik Rijkers (#4)
1 attachment(s)
Re: tests for client programs

On Wed, 2014-01-15 at 20:56 +0100, Erik Rijkers wrote:

2 tests stumbled:

1. One test ( pg_ctl/t/001_start_stop.pl ) failed because I had PGDATA set. I unset all PG+ vars after that. No a big
problem but nonetheless it might be better if the test suite removes /controls the variables before running.

2. The pg_isready test failed command_fails() ('fails with no server running') because it defaults to the compiled-in
server-port (and that server was running). I added the test-designated port (65432, as defined in TestLib.pm). This
simple change is in the attached patch.

With these two changes the whole test suite passed.

Fixed those two things by unsetting environment variables and picking a
different port.

New patch attached.

Attachments:

v2-0001-Add-TAP-tests-for-client-programs.patchtext/x-patch; charset=UTF-8; name=v2-0001-Add-TAP-tests-for-client-programs.patchDownload
>From 5af8f6849506b993ab7cb3fc8bb167d2f93424fc Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Sat, 8 Feb 2014 22:05:09 -0500
Subject: [PATCH v2] Add TAP tests for client programs

---
 GNUmakefile.in                                 |   4 +-
 configure                                      |  47 +++++++
 configure.in                                   |   5 +
 src/Makefile.global.in                         |  19 ++-
 src/bin/initdb/.gitignore                      |   2 +
 src/bin/initdb/Makefile                        |   7 +
 src/bin/initdb/t/001_initdb.pl                 |  37 +++++
 src/bin/pg_basebackup/.gitignore               |   2 +
 src/bin/pg_basebackup/Makefile                 |   6 +
 src/bin/pg_basebackup/t/010_pg_basebackup.pl   |  67 ++++++++++
 src/bin/pg_basebackup/t/020_pg_receivexlog.pl  |   8 ++
 src/bin/pg_config/.gitignore                   |   1 +
 src/bin/pg_config/Makefile                     |   6 +
 src/bin/pg_config/t/001_pg_config.pl           |  12 ++
 src/bin/pg_controldata/.gitignore              |   1 +
 src/bin/pg_controldata/Makefile                |   6 +
 src/bin/pg_controldata/t/001_pg_controldata.pl |  14 ++
 src/bin/pg_ctl/.gitignore                      |   1 +
 src/bin/pg_ctl/Makefile                        |   6 +
 src/bin/pg_ctl/t/001_start_stop.pl             |  21 +++
 src/bin/pg_ctl/t/002_status.pl                 |  14 ++
 src/bin/scripts/.gitignore                     |   2 +
 src/bin/scripts/Makefile                       |   7 +
 src/bin/scripts/t/010_clusterdb.pl             |  18 +++
 src/bin/scripts/t/011_clusterdb_all.pl         |   9 ++
 src/bin/scripts/t/020_createdb.pl              |  16 +++
 src/bin/scripts/t/030_createlang.pl            |  18 +++
 src/bin/scripts/t/040_createuser.pl            |  26 ++++
 src/bin/scripts/t/050_dropdb.pl                |  16 +++
 src/bin/scripts/t/060_droplang.pl              |  15 +++
 src/bin/scripts/t/070_dropuser.pl              |  16 +++
 src/bin/scripts/t/080_pg_isready.pl            |  15 +++
 src/bin/scripts/t/090_reindexdb.pl             |  21 +++
 src/bin/scripts/t/091_reindexdb_all.pl         |  11 ++
 src/bin/scripts/t/100_vacuumdb.pl              |  17 +++
 src/bin/scripts/t/101_vacuumdb_all.pl          |   9 ++
 src/test/perl/TestLib.pm                       | 178 +++++++++++++++++++++++++
 37 files changed, 677 insertions(+), 3 deletions(-)
 create mode 100644 src/bin/initdb/t/001_initdb.pl
 create mode 100644 src/bin/pg_basebackup/t/010_pg_basebackup.pl
 create mode 100644 src/bin/pg_basebackup/t/020_pg_receivexlog.pl
 create mode 100644 src/bin/pg_config/t/001_pg_config.pl
 create mode 100644 src/bin/pg_controldata/t/001_pg_controldata.pl
 create mode 100644 src/bin/pg_ctl/t/001_start_stop.pl
 create mode 100644 src/bin/pg_ctl/t/002_status.pl
 create mode 100644 src/bin/scripts/t/010_clusterdb.pl
 create mode 100644 src/bin/scripts/t/011_clusterdb_all.pl
 create mode 100644 src/bin/scripts/t/020_createdb.pl
 create mode 100644 src/bin/scripts/t/030_createlang.pl
 create mode 100644 src/bin/scripts/t/040_createuser.pl
 create mode 100644 src/bin/scripts/t/050_dropdb.pl
 create mode 100644 src/bin/scripts/t/060_droplang.pl
 create mode 100644 src/bin/scripts/t/070_dropuser.pl
 create mode 100644 src/bin/scripts/t/080_pg_isready.pl
 create mode 100644 src/bin/scripts/t/090_reindexdb.pl
 create mode 100644 src/bin/scripts/t/091_reindexdb_all.pl
 create mode 100644 src/bin/scripts/t/100_vacuumdb.pl
 create mode 100644 src/bin/scripts/t/101_vacuumdb_all.pl
 create mode 100644 src/test/perl/TestLib.pm

diff --git a/GNUmakefile.in b/GNUmakefile.in
index 40ab280..3910abb 100644
--- a/GNUmakefile.in
+++ b/GNUmakefile.in
@@ -66,9 +66,9 @@ check check-tests: all
 check check-tests installcheck installcheck-parallel installcheck-tests:
 	$(MAKE) -C src/test/regress $@
 
-$(call recurse,check-world,src/test src/pl src/interfaces/ecpg contrib,check)
+$(call recurse,check-world,src/test src/pl src/interfaces/ecpg contrib src/bin,check)
 
-$(call recurse,installcheck-world,src/test src/pl src/interfaces/ecpg contrib,installcheck)
+$(call recurse,installcheck-world,src/test src/pl src/interfaces/ecpg contrib src/bin,installcheck)
 
 GNUmakefile: GNUmakefile.in $(top_builddir)/config.status
 	./config.status $@
diff --git a/configure b/configure
index 6ad165f..368545f 100755
--- a/configure
+++ b/configure
@@ -627,6 +627,7 @@ ac_includes_default="\
 
 ac_subst_vars='LTLIBOBJS
 vpath_build
+PROVE
 OSX
 XSLTPROC
 COLLATEINDEX
@@ -14354,6 +14355,52 @@ fi
 done
 
 
+#
+# Check for test tools
+#
+for ac_prog in prove
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_PROVE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$PROVE"; then
+  ac_cv_prog_PROVE="$PROVE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_PROVE="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+PROVE=$ac_cv_prog_PROVE
+if test -n "$PROVE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROVE" >&5
+$as_echo "$PROVE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$PROVE" && break
+done
+
+
 # Thread testing
 
 # We have to run the thread test near the end so we have all our symbols
diff --git a/configure.in b/configure.in
index aa23f9b..f06857e 100644
--- a/configure.in
+++ b/configure.in
@@ -1817,6 +1817,11 @@ PGAC_PATH_COLLATEINDEX
 AC_CHECK_PROGS(XSLTPROC, xsltproc)
 AC_CHECK_PROGS(OSX, [osx sgml2xml sx])
 
+#
+# Check for test tools
+#
+AC_CHECK_PROGS(PROVE, prove)
+
 # Thread testing
 
 # We have to run the thread test near the end so we have all our symbols
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index e0e9b79..cd7f4db 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -1,5 +1,6 @@
 # -*-makefile-*-
 # src/Makefile.global.in
+# @configure_input@
 
 #------------------------------------------------------------------------------
 # All PostgreSQL makefiles include this file and use the variables it sets,
@@ -292,6 +293,22 @@ XGETTEXT = @XGETTEXT@
 GZIP	= gzip
 BZIP2	= bzip2
 
+# Testing
+
+PROVE = @PROVE@
+PG_PROVE_FLAGS = --ext='.pl' -I $(top_srcdir)/src/test/perl/
+PROVE_FLAGS = --verbose
+
+define prove_installcheck
+PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
+endef
+
+define prove_check
+$(MKDIR_P) tmp_check/log
+$(MAKE) -C $(top_builddir) DESTDIR=$(CURDIR)/tmp_check/install install >$(CURDIR)/tmp_check/log/install.log 2>&1
+PATH="$(CURDIR)/tmp_check/install$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
+endef
+
 # Installation.
 
 install_bin = @install_bin@
@@ -755,7 +772,7 @@ gcda_files := $(wildcard *.gcda)
 
 lcov.info: $(gcda_files)
 	rm -f *.gcov
-	$(if $^,$(LCOV) -d . -c -o $@ $(LCOVFLAGS))
+	$(if $^,$(LCOV) -d . -c -o $@ $(LCOVFLAGS) --gcov-tool $(GCOV))
 
 %.c.gcov: %.gcda | lcov.info
 	$(GCOV) -b -f -p -o . $(GCOVFLAGS) $*.c >$*.c.gcov.out
diff --git a/src/bin/initdb/.gitignore b/src/bin/initdb/.gitignore
index 0f74727..71a899f 100644
--- a/src/bin/initdb/.gitignore
+++ b/src/bin/initdb/.gitignore
@@ -2,3 +2,5 @@
 /localtime.c
 
 /initdb
+
+/tmp_check/
diff --git a/src/bin/initdb/Makefile b/src/bin/initdb/Makefile
index 1ece7ac..fb7d142 100644
--- a/src/bin/initdb/Makefile
+++ b/src/bin/initdb/Makefile
@@ -57,3 +57,10 @@ clean distclean maintainer-clean:
 
 # ensure that changes in datadir propagate into object file
 initdb.o: initdb.c $(top_builddir)/src/Makefile.global
+
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
new file mode 100644
index 0000000..ad6d74c
--- /dev/null
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -0,0 +1,37 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 14;
+
+my $tempdir = TestLib::tempdir;
+
+program_help_ok('initdb');
+program_version_ok('initdb');
+program_options_handling_ok('initdb');
+
+command_ok(['initdb', "$tempdir/data"], 'basic initdb');
+command_fails(['initdb', "$tempdir/data"], 'existing data directory');
+command_ok(['initdb', '-N', "$tempdir/data2"], 'nosync');
+command_ok(['initdb', '-S', "$tempdir/data2"], 'sync only');
+command_fails(['initdb', '-S', "$tempdir/data3"], 'sync missing data directory');
+mkdir "$tempdir/data4" or BAIL_OUT($!);
+command_ok(['initdb', "$tempdir/data4"], 'existing empty data directory');
+
+system_or_bail "rm -rf $tempdir/*";
+
+command_ok(['initdb', "$tempdir/data", '-X', "$tempdir/pgxlog"], 'separate xlog directory');
+
+system_or_bail "rm -rf $tempdir/*";
+command_fails(['initdb', "$tempdir/data", '-X', 'pgxlog'], 'relative xlog directory not allowed');
+
+system_or_bail "rm -rf $tempdir/*";
+mkdir "$tempdir/pgxlog";
+command_ok(['initdb', "$tempdir/data", '-X', "$tempdir/pgxlog"], 'existing empty xlog directory');
+
+system_or_bail "rm -rf $tempdir/*";
+mkdir "$tempdir/pgxlog";
+mkdir "$tempdir/pgxlog/lost+found";
+command_fails(['initdb', "$tempdir/data", '-X', "$tempdir/pgxlog"], 'existing nonempty xlog directory');
+
+system_or_bail "rm -rf $tempdir/*";
+command_ok(['initdb', "$tempdir/data", '-T', 'german'], 'select default dictionary');
diff --git a/src/bin/pg_basebackup/.gitignore b/src/bin/pg_basebackup/.gitignore
index 1334a1f..17c1731 100644
--- a/src/bin/pg_basebackup/.gitignore
+++ b/src/bin/pg_basebackup/.gitignore
@@ -1,2 +1,4 @@
 /pg_basebackup
 /pg_receivexlog
+
+/tmp_check/
diff --git a/src/bin/pg_basebackup/Makefile b/src/bin/pg_basebackup/Makefile
index 17c91af..1b8d411 100644
--- a/src/bin/pg_basebackup/Makefile
+++ b/src/bin/pg_basebackup/Makefile
@@ -41,3 +41,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_basebackup$(X) pg_receivexlog$(X) $(OBJS) pg_basebackup.o pg_receivexlog.o
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
new file mode 100644
index 0000000..b4c0471
--- /dev/null
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -0,0 +1,67 @@
+use strict;
+use warnings;
+use Cwd;
+use TestLib;
+use Test::More tests => 19;
+
+program_help_ok('pg_basebackup');
+program_version_ok('pg_basebackup');
+program_options_handling_ok('pg_basebackup');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+command_fails(['pg_basebackup'], 'pg_basebackup needs target directory specified');
+command_fails(['pg_basebackup', '-D', "$tempdir/backup"], 'pg_basebackup fails because of hba');
+
+open HBA, ">>$tempdir/pgdata/pg_hba.conf";
+print HBA "local replication all trust\n";
+print HBA "host replication all 127.0.0.1/32 trust\n";
+print HBA "host replication all ::1/128 trust\n";
+close HBA;
+system_or_bail 'pg_ctl', '-s', '-D', "$tempdir/pgdata", 'reload';
+
+command_fails(['pg_basebackup', '-D', "$tempdir/backup"], 'pg_basebackup fails because of WAL configuration');
+
+open CONF, ">>$tempdir/pgdata/postgresql.conf";
+print CONF "max_wal_senders = 10\n";
+print CONF "wal_level = archive\n";
+close CONF;
+restart_test_server;
+
+command_ok(['pg_basebackup', '-D', "$tempdir/backup"], 'pg_basebackup runs');
+ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+
+command_ok(['pg_basebackup', '-D', "$tempdir/backup2", '--xlogdir', "$tempdir/xlog2"], 'separate xlog directory');
+ok(-f "$tempdir/backup2/PG_VERSION", 'backup was created');
+ok(-d "$tempdir/xlog2/", 'xlog directory was created');
+
+command_ok(['pg_basebackup', '-D', "$tempdir/tarbackup", '-Ft'], 'tar format');
+ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
+
+mkdir "$tempdir/tblspc1";
+psql 'postgres', "CREATE TABLESPACE tblspc1 LOCATION '$tempdir/tblspc1';";
+psql 'postgres', "CREATE TABLE test1 (a int) TABLESPACE tblspc1;";
+command_ok(['pg_basebackup', '-D', "$tempdir/tarbackup2", '-Ft'], 'tar format with tablespaces');
+ok(-f "$tempdir/tarbackup2/base.tar", 'backup tar was created');
+my @tblspc_tars = glob "$tempdir/tarbackup2/[0-9]*.tar";
+note("tablespace tars are @tblspc_tars");
+is(scalar(@tblspc_tars), 1, 'one tablespace tar was created');
+
+
+our $TODO = 'https://commitfest.postgresql.org/action/patch_view?id=1303';
+
+command_fails(['pg_basebackup', '-D', "$tempdir/same", '--xlogdir', "$tempdir/same"],
+			  'fails if data and xlog directory are the same');
+
+my $pwd = cwd();
+chdir $tempdir or BAIL_OUT("could not chdir to $tempdir: $!");
+
+command_fails(['pg_basebackup', '-D', "$tempdir/same2", '--xlogdir', "same2"],
+			  'fails if data and xlog directory are the same, relative xlog directory');
+command_fails(['pg_basebackup', '-D', "same3", '--xlogdir', "$tempdir/same3"],
+			  'fails if data and xlog directory are the same, relative data directory');
+
+chdir $pwd or BAIL_OUT("could not chdir to $pwd: $!");;
+
+$TODO = undef;
diff --git a/src/bin/pg_basebackup/t/020_pg_receivexlog.pl b/src/bin/pg_basebackup/t/020_pg_receivexlog.pl
new file mode 100644
index 0000000..700ae04
--- /dev/null
+++ b/src/bin/pg_basebackup/t/020_pg_receivexlog.pl
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 3;
+
+program_help_ok('pg_receivexlog');
+program_version_ok('pg_receivexlog');
+program_options_handling_ok('pg_receivexlog');
diff --git a/src/bin/pg_config/.gitignore b/src/bin/pg_config/.gitignore
index 169bc76..cc42247 100644
--- a/src/bin/pg_config/.gitignore
+++ b/src/bin/pg_config/.gitignore
@@ -1 +1,2 @@
 /pg_config
+/tmp_check/
diff --git a/src/bin/pg_config/Makefile b/src/bin/pg_config/Makefile
index 578a2a5..579dce6 100644
--- a/src/bin/pg_config/Makefile
+++ b/src/bin/pg_config/Makefile
@@ -47,3 +47,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_config$(X) $(OBJS)
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_config/t/001_pg_config.pl b/src/bin/pg_config/t/001_pg_config.pl
new file mode 100644
index 0000000..200f394
--- /dev/null
+++ b/src/bin/pg_config/t/001_pg_config.pl
@@ -0,0 +1,12 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 7;
+
+program_help_ok('pg_config');
+program_version_ok('pg_config');
+program_options_handling_ok('pg_config');
+command_like(['pg_config', '--bindir'], qr/bin/, 'pg_config single option');  # XXX might be wrong
+command_like(['pg_config', '--bindir', '--libdir'],  qr/bin.*\n.*lib/, 'pg_config two options');
+command_like(['pg_config', '--libdir', '--bindir'],  qr/lib.*\n.*bin/, 'pg_config two options different order');
+command_like(['pg_config'], qr/.*\n.*\n.*/, 'pg_config without options prints many lines');
diff --git a/src/bin/pg_controldata/.gitignore b/src/bin/pg_controldata/.gitignore
index eab0c28..051d71d 100644
--- a/src/bin/pg_controldata/.gitignore
+++ b/src/bin/pg_controldata/.gitignore
@@ -1 +1,2 @@
 /pg_controldata
+/tmp_check/
diff --git a/src/bin/pg_controldata/Makefile b/src/bin/pg_controldata/Makefile
index 8b5f340..5abe76c 100644
--- a/src/bin/pg_controldata/Makefile
+++ b/src/bin/pg_controldata/Makefile
@@ -33,3 +33,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_controldata$(X) $(OBJS)
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
new file mode 100644
index 0000000..3ddab80
--- /dev/null
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -0,0 +1,14 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+my $tempdir = TestLib::tempdir;
+
+program_help_ok('pg_controldata');
+program_version_ok('pg_controldata');
+program_options_handling_ok('pg_controldata');
+command_fails(['pg_controldata'], 'pg_controldata without arguments fails');
+command_fails(['pg_controldata', 'nonexistent'], 'pg_controldata with nonexistent directory fails');
+system_or_bail "initdb -D $tempdir/data -A trust";
+command_like(['pg_controldata', "$tempdir/data"], qr/checkpoint/, 'pg_controldata produces output');
diff --git a/src/bin/pg_ctl/.gitignore b/src/bin/pg_ctl/.gitignore
index c90c103..73ab4ed 100644
--- a/src/bin/pg_ctl/.gitignore
+++ b/src/bin/pg_ctl/.gitignore
@@ -1 +1,2 @@
 /pg_ctl
+/tmp_check/
diff --git a/src/bin/pg_ctl/Makefile b/src/bin/pg_ctl/Makefile
index cbc1638..a9a0366 100644
--- a/src/bin/pg_ctl/Makefile
+++ b/src/bin/pg_ctl/Makefile
@@ -36,3 +36,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_ctl$(X) $(OBJS)
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
new file mode 100644
index 0000000..6818ba4
--- /dev/null
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 10;
+
+my $tempdir = TestLib::tempdir;
+
+program_help_ok('pg_ctl');
+program_version_ok('pg_ctl');
+program_options_handling_ok('pg_ctl');
+
+command_ok(['pg_ctl', 'initdb', '-D', "$tempdir/data"], 'pg_ctl initdb');
+command_ok(['pg_ctl', 'start', '-D', "$tempdir/data", '-w'], 'pg_ctl start -w');
+command_ok(['pg_ctl', 'start', '-D', "$tempdir/data", '-w'], 'second pg_ctl start succeeds');
+command_ok(['pg_ctl', 'stop', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'pg_ctl stop -w');
+command_fails(['pg_ctl', 'stop', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'second pg_ctl stop fails');
+
+command_ok(['pg_ctl', 'restart', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'pg_ctl restart with server not running');
+command_ok(['pg_ctl', 'restart', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'pg_ctl restart with server running');
+
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data", '-m', 'fast';
diff --git a/src/bin/pg_ctl/t/002_status.pl b/src/bin/pg_ctl/t/002_status.pl
new file mode 100644
index 0000000..53524eb
--- /dev/null
+++ b/src/bin/pg_ctl/t/002_status.pl
@@ -0,0 +1,14 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 2;
+
+my $tempdir = TestLib::tempdir;
+
+system_or_bail "initdb -D $tempdir/data -A trust";
+command_exit_is(['pg_ctl', 'status', '-D', "$tempdir/data"], 3, 'pg_ctl status with server not running');
+
+system_or_bail 'pg_ctl', '-s', '-l', "$tempdir/logfile", '-D', "$tempdir/data", '-w', 'start';
+command_exit_is(['pg_ctl', 'status', '-D', "$tempdir/data"], 0, 'pg_ctl status with server running');
+
+system_or_bail 'pg_ctl', '-s', 'stop', '-D', "$tempdir/data", '-m', 'fast';
diff --git a/src/bin/scripts/.gitignore b/src/bin/scripts/.gitignore
index 0b9b786..1056b28 100644
--- a/src/bin/scripts/.gitignore
+++ b/src/bin/scripts/.gitignore
@@ -14,3 +14,5 @@
 /kwlookup.c
 /mbprint.c
 /print.c
+
+/tmp_check/
diff --git a/src/bin/scripts/Makefile b/src/bin/scripts/Makefile
index b5d9207..de0e11f 100644
--- a/src/bin/scripts/Makefile
+++ b/src/bin/scripts/Makefile
@@ -68,3 +68,10 @@ clean distclean maintainer-clean:
 	rm -f $(addsuffix $(X), $(PROGRAMS)) $(addsuffix .o, $(PROGRAMS))
 	rm -f common.o dumputils.o kwlookup.o keywords.o print.o mbprint.o $(WIN32RES)
 	rm -f dumputils.c print.c mbprint.c kwlookup.c keywords.c
+
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/scripts/t/010_clusterdb.pl b/src/bin/scripts/t/010_clusterdb.pl
new file mode 100644
index 0000000..371b2dd
--- /dev/null
+++ b/src/bin/scripts/t/010_clusterdb.pl
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+program_help_ok('clusterdb');
+program_version_ok('clusterdb');
+program_options_handling_ok('clusterdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['clusterdb', 'postgres'], qr/statement: CLUSTER;/, 'SQL CLUSTER run');
+
+command_fails(['clusterdb', '-t', 'nonexistent', 'postgres'], 'fails with nonexistent table');
+
+psql 'postgres', 'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a); CLUSTER test1 USING test1x';
+issues_sql_like(['clusterdb', 'postgres', '-t', 'test1'], qr/statement: CLUSTER test1;/, 'cluster specific table');
diff --git a/src/bin/scripts/t/011_clusterdb_all.pl b/src/bin/scripts/t/011_clusterdb_all.pl
new file mode 100644
index 0000000..304c4be
--- /dev/null
+++ b/src/bin/scripts/t/011_clusterdb_all.pl
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 1;
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['clusterdb', '-a'], qr/statement: CLUSTER.*statement: CLUSTER/s, 'cluster all databases');
diff --git a/src/bin/scripts/t/020_createdb.pl b/src/bin/scripts/t/020_createdb.pl
new file mode 100644
index 0000000..8b82a2b
--- /dev/null
+++ b/src/bin/scripts/t/020_createdb.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+program_help_ok('createdb');
+program_version_ok('createdb');
+program_options_handling_ok('createdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['createdb', 'foobar1'], qr/statement: CREATE DATABASE foobar1/, 'SQL CREATE DATABASE run');
+issues_sql_like(['createdb', 'foobar2', '-l', 'C', '-E', 'LATIN1', '-T', 'template0'], qr/statement: CREATE DATABASE foobar2 ENCODING 'LATIN1'/, 'create database with encoding');
+
+command_fails(['createdb', 'foobar1'], 'fails if database already exists');
diff --git a/src/bin/scripts/t/030_createlang.pl b/src/bin/scripts/t/030_createlang.pl
new file mode 100644
index 0000000..9a87f4c
--- /dev/null
+++ b/src/bin/scripts/t/030_createlang.pl
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+program_help_ok('createlang');
+program_version_ok('createlang');
+program_options_handling_ok('createlang');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+command_fails(['createlang', 'plpgsql', 'postgres'], 'fails if language already exists');
+
+psql 'postgres', 'DROP EXTENSION plpgsql';
+issues_sql_like(['createlang', 'plpgsql', 'postgres'], qr/statement: CREATE EXTENSION "plpgsql"/, 'SQL CREATE EXTENSION run');
+
+command_like(['createlang', '--list', 'postgres'], qr/plpgsql/, 'list output');
diff --git a/src/bin/scripts/t/040_createuser.pl b/src/bin/scripts/t/040_createuser.pl
new file mode 100644
index 0000000..922873a
--- /dev/null
+++ b/src/bin/scripts/t/040_createuser.pl
@@ -0,0 +1,26 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 8;
+
+program_help_ok('createuser');
+program_version_ok('createuser');
+program_options_handling_ok('createuser');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['createuser', 'user1'],
+				qr/statement: CREATE ROLE user1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN;/,
+				'SQL CREATE USER run');
+issues_sql_like(['createuser', '-L', 'role1'],
+				qr/statement: CREATE ROLE role1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN;/,
+				'create a non-login role');
+issues_sql_like(['createuser', '-r', 'user2'],
+				qr/statement: CREATE ROLE user2 NOSUPERUSER NOCREATEDB CREATEROLE INHERIT LOGIN;/,
+				'create a CREATEROLE user');
+issues_sql_like(['createuser', '-s', 'user3'],
+				qr/statement: CREATE ROLE user3 SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;/,
+				'create a superuser');
+
+command_fails(['createuser', 'user1'], 'fails if role already exists');
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
new file mode 100644
index 0000000..3662dd0
--- /dev/null
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('dropdb');
+program_version_ok('dropdb');
+program_options_handling_ok('dropdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+psql 'postgres', 'CREATE DATABASE foobar1';
+issues_sql_like(['dropdb', 'foobar1'], qr/statement: DROP DATABASE foobar1/, 'SQL DROP DATABASE run');
+
+command_fails(['dropdb', 'nonexistent'], 'fails with nonexistent database');
diff --git a/src/bin/scripts/t/060_droplang.pl b/src/bin/scripts/t/060_droplang.pl
new file mode 100644
index 0000000..47cb48f
--- /dev/null
+++ b/src/bin/scripts/t/060_droplang.pl
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('droplang');
+program_version_ok('droplang');
+program_options_handling_ok('droplang');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['droplang', 'plpgsql', 'postgres'], qr/statement: DROP EXTENSION "plpgsql"/, 'SQL DROP EXTENSION run');
+
+command_fails(['droplang', 'nonexistent', 'postgres'], 'fails with nonexistent language');
diff --git a/src/bin/scripts/t/070_dropuser.pl b/src/bin/scripts/t/070_dropuser.pl
new file mode 100644
index 0000000..495636a
--- /dev/null
+++ b/src/bin/scripts/t/070_dropuser.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('dropuser');
+program_version_ok('dropuser');
+program_options_handling_ok('dropuser');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+psql 'postgres', 'CREATE ROLE foobar1';
+issues_sql_like(['dropuser', 'foobar1'], qr/statement: DROP ROLE foobar1/, 'SQL DROP ROLE run');
+
+command_fails(['dropuser', 'nonexistent'], 'fails with nonexistent user');
diff --git a/src/bin/scripts/t/080_pg_isready.pl b/src/bin/scripts/t/080_pg_isready.pl
new file mode 100644
index 0000000..03c3657
--- /dev/null
+++ b/src/bin/scripts/t/080_pg_isready.pl
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('pg_isready');
+program_version_ok('pg_isready');
+program_options_handling_ok('pg_isready');
+
+command_fails(['pg_isready'], 'fails with no server running');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+command_ok(['pg_isready'], 'succeeds with server running');
diff --git a/src/bin/scripts/t/090_reindexdb.pl b/src/bin/scripts/t/090_reindexdb.pl
new file mode 100644
index 0000000..18756e8
--- /dev/null
+++ b/src/bin/scripts/t/090_reindexdb.pl
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 7;
+
+program_help_ok('reindexdb');
+program_version_ok('reindexdb');
+program_options_handling_ok('reindexdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+$ENV{PGOPTIONS} = '--client-min-messages=WARNING';
+
+issues_sql_like(['reindexdb', 'postgres'], qr/statement: REINDEX DATABASE postgres;/, 'SQL REINDEX run');
+
+psql 'postgres', 'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a);';
+issues_sql_like(['reindexdb', 'postgres', '-t', 'test1'], qr/statement: REINDEX TABLE test1;/, 'reindex specific table');
+issues_sql_like(['reindexdb', 'postgres', '-i', 'test1x'], qr/statement: REINDEX INDEX test1x;/, 'reindex specific index');
+
+issues_sql_like(['reindexdb', 'postgres', '-s'], qr/statement: REINDEX SYSTEM postgres;/, 'reindex system tables');
diff --git a/src/bin/scripts/t/091_reindexdb_all.pl b/src/bin/scripts/t/091_reindexdb_all.pl
new file mode 100644
index 0000000..eee8ba8
--- /dev/null
+++ b/src/bin/scripts/t/091_reindexdb_all.pl
@@ -0,0 +1,11 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 1;
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+$ENV{PGOPTIONS} = '--client-min-messages=WARNING';
+
+issues_sql_like(['reindexdb', '-a'], qr/statement: REINDEX.*statement: REINDEX/s, 'reindex all databases');
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
new file mode 100644
index 0000000..39f1cec
--- /dev/null
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 8;
+
+program_help_ok('vacuumdb');
+program_version_ok('vacuumdb');
+program_options_handling_ok('vacuumdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['vacuumdb', 'postgres'], qr/statement: VACUUM;/, 'SQL VACUUM run');
+issues_sql_like(['vacuumdb', '-f', 'postgres'], qr/statement: VACUUM \(FULL\);/, 'vacuumdb -f');
+issues_sql_like(['vacuumdb', '-F', 'postgres'], qr/statement: VACUUM \(FREEZE\);/, 'vacuumdb -F');
+issues_sql_like(['vacuumdb', '-z', 'postgres'], qr/statement: VACUUM \(ANALYZE\);/, 'vacuumdb -z');
+issues_sql_like(['vacuumdb', '-Z', 'postgres'], qr/statement: ANALYZE;/, 'vacuumdb -z');
diff --git a/src/bin/scripts/t/101_vacuumdb_all.pl b/src/bin/scripts/t/101_vacuumdb_all.pl
new file mode 100644
index 0000000..b5779bc
--- /dev/null
+++ b/src/bin/scripts/t/101_vacuumdb_all.pl
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 1;
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['vacuumdb', '-a'], qr/statement: VACUUM.*statement: VACUUM/s, 'vacuum all databases');
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
new file mode 100644
index 0000000..574ab78
--- /dev/null
+++ b/src/test/perl/TestLib.pm
@@ -0,0 +1,178 @@
+package TestLib;
+
+use strict;
+use warnings;
+
+use Exporter 'import';
+our @EXPORT = qw(
+	tempdir
+	start_test_server
+	restart_test_server
+	psql
+	system_or_bail
+
+	command_ok
+	command_fails
+	command_exit_is
+	program_help_ok
+	program_version_ok
+	program_options_handling_ok
+	command_like
+	issues_sql_like
+);
+
+use Cwd;
+use File::Spec;
+use File::Temp ();
+use IPC::Run qw(run start);
+use Test::More;
+
+delete $ENV{PGCONNECT_TIMEOUT};
+delete $ENV{PGDATA};
+delete $ENV{PGDATABASE};
+delete $ENV{PGHOSTADDR};
+delete $ENV{PGREQUIRESSL};
+delete $ENV{PGSERVICE};
+delete $ENV{PGSSLMODE};
+delete $ENV{PGUSER};
+
+if (!$ENV{PGPORT}) {
+	$ENV{PGPORT} = 65432;
+}
+
+$ENV{PGPORT} = int($ENV{PGPORT}) % 65536;
+
+
+#
+# Helper functions
+#
+
+
+sub tempdir {
+	return File::Temp::tempdir('testXXXX', DIR => cwd(), CLEANUP => 1);
+}
+
+my ($test_server_datadir, $test_server_logfile);
+
+sub start_test_server {
+	my ($tempdir) = @_;
+	my $ret;
+
+	system "initdb -D $tempdir/pgdata -A trust -N >/dev/null";
+	$ret = system 'pg_ctl', '-D', "$tempdir/pgdata", '-s', '-w', '-l', "$tempdir/logfile", '-o', "--fsync=off -k $tempdir --log-statement=all", 'start';
+
+	if ($ret != 0) {
+		system('cat', "$tempdir/logfile");
+		BAIL_OUT("pg_ctl failed");
+	}
+
+	$ENV{PGHOST} = $tempdir;
+	$test_server_datadir = "$tempdir/pgdata";
+	$test_server_logfile = "$tempdir/logfile";
+}
+
+sub restart_test_server {
+	system 'pg_ctl', '-s', '-D', $test_server_datadir, '-w', '-l', $test_server_logfile, 'restart';
+}
+
+END {
+	if ($test_server_datadir) {
+		system 'pg_ctl', '-D', $test_server_datadir, '-s', '-w', '-m', 'immediate', 'stop';
+	}
+}
+
+sub psql {
+	my ($dbname, $sql) = @_;
+	run ['psql', '-X', '-q', '-d', $dbname, '-f', '-'], '<', \$sql or die;
+}
+
+sub system_or_bail {
+	system(@_) == 0 or BAIL_OUT("system @_ failed: $?");
+}
+
+
+#
+# Test functions
+#
+
+
+sub command_ok {
+	my ($cmd, $test_name) = @_;
+	#my $result = run $cmd, '>', File::Spec->devnull(), '2>', File::Spec->devnull();
+	my $result = run $cmd;
+	ok($result, $test_name);
+}
+
+sub command_fails {
+	my ($cmd, $test_name) = @_;
+	my $result = run $cmd, '>', File::Spec->devnull(), '2>', File::Spec->devnull();
+	ok(!$result, $test_name);
+}
+
+sub command_exit_is {
+	my ($cmd, $expected, $test_name) = @_;
+	my $h = start $cmd, '>', File::Spec->devnull(), '2>', File::Spec->devnull();
+	$h->finish();
+	is($h->result(0), $expected, $test_name);
+}
+
+sub program_help_ok {
+	my ($cmd) = @_;
+	subtest "$cmd --help" => sub {
+		plan tests => 3;
+		my ($stdout, $stderr);
+		my $result = run [$cmd, '--help'], '>', \$stdout, '2>', \$stderr;
+		ok($result, "$cmd --help exit code 0");
+		isnt($stdout, '', "$cmd --help goes to stdout");
+		is($stderr, '', "$cmd --help nothing to stderr");
+	};
+}
+
+sub program_version_ok {
+	my ($cmd) = @_;
+	subtest "$cmd --version" => sub {
+		plan tests => 3;
+		my ($stdout, $stderr);
+		my $result = run [$cmd, '--version'], '>', \$stdout, '2>', \$stderr;
+		ok($result, "$cmd --version exit code 0");
+		isnt($stdout, '', "$cmd --version goes to stdout");
+		is($stderr, '', "$cmd --version nothing to stderr");
+	};
+}
+
+sub program_options_handling_ok {
+	my ($cmd) = @_;
+	subtest "$cmd options handling" => sub {
+		plan tests => 2;
+		my ($stdout, $stderr);
+		my $result = run [$cmd, '--not-a-valid-option'], '>', \$stdout, '2>', \$stderr;
+		ok(!$result, "$cmd with invalid option nonzero exit code");
+		isnt($stderr, '', "$cmd with invalid option prints error message");
+	};
+}
+
+sub command_like {
+	my ($cmd, $expected_stdout, $test_name) = @_;
+	subtest $test_name => sub {
+		plan tests => 3;
+		my ($stdout, $stderr);
+		my $result = run $cmd, '>', \$stdout, '2>', \$stderr;
+		ok($result, "@$cmd exit code 0");
+		is($stderr, '', "@$cmd no stderr");
+		like($stdout, $expected_stdout, "$test_name: matches");
+	};
+}
+
+sub issues_sql_like {
+	my ($cmd, $expected_sql, $test_name) = @_;
+	subtest $test_name => sub {
+		plan tests => 2;
+		truncate $test_server_logfile, 0;
+		my $result = run $cmd;
+		ok($result, "@$cmd exit code 0");
+		my $log = `cat $test_server_logfile`;
+		like($log, $expected_sql, "$test_name: SQL found in server log");
+	};
+}
+
+1;
-- 
1.8.5.1

#6Peter Eisentraut
peter_e@gmx.net
In reply to: Peter Eisentraut (#5)
1 attachment(s)
Re: tests for client programs

Updated patch. Changes:

- added documentation
- avoid port conflicts with running instances
- added tests for pg_basebackup -T
- removed TODO tests for rejected pg_basebackup feature

A test on Windows would be nice. Otherwise we'll let the buildfarm do it.

Attachments:

v3-0001-Add-TAP-tests-for-client-programs.patchtext/x-patch; name=v3-0001-Add-TAP-tests-for-client-programs.patchDownload
>From 8205e58442720965c98794cb2f234c46b70dada7 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 27 Feb 2014 21:39:35 -0500
Subject: [PATCH v3] Add TAP tests for client programs

---
 GNUmakefile.in                                 |   4 +-
 configure                                      |  47 +++++++
 configure.in                                   |   5 +
 doc/src/sgml/installation.sgml                 |   3 +-
 doc/src/sgml/regress.sgml                      |  28 ++++
 src/Makefile.global.in                         |  19 ++-
 src/bin/initdb/.gitignore                      |   2 +
 src/bin/initdb/Makefile                        |   7 +
 src/bin/initdb/t/001_initdb.pl                 |  37 +++++
 src/bin/pg_basebackup/.gitignore               |   2 +
 src/bin/pg_basebackup/Makefile                 |   6 +
 src/bin/pg_basebackup/t/010_pg_basebackup.pl   |  91 ++++++++++++
 src/bin/pg_basebackup/t/020_pg_receivexlog.pl  |   8 ++
 src/bin/pg_config/.gitignore                   |   1 +
 src/bin/pg_config/Makefile                     |   6 +
 src/bin/pg_config/t/001_pg_config.pl           |  12 ++
 src/bin/pg_controldata/.gitignore              |   1 +
 src/bin/pg_controldata/Makefile                |   6 +
 src/bin/pg_controldata/t/001_pg_controldata.pl |  14 ++
 src/bin/pg_ctl/.gitignore                      |   1 +
 src/bin/pg_ctl/Makefile                        |   6 +
 src/bin/pg_ctl/t/001_start_stop.pl             |  25 ++++
 src/bin/pg_ctl/t/002_status.pl                 |  19 +++
 src/bin/scripts/.gitignore                     |   2 +
 src/bin/scripts/Makefile                       |   7 +
 src/bin/scripts/t/010_clusterdb.pl             |  18 +++
 src/bin/scripts/t/011_clusterdb_all.pl         |   9 ++
 src/bin/scripts/t/020_createdb.pl              |  16 +++
 src/bin/scripts/t/030_createlang.pl            |  18 +++
 src/bin/scripts/t/040_createuser.pl            |  26 ++++
 src/bin/scripts/t/050_dropdb.pl                |  16 +++
 src/bin/scripts/t/060_droplang.pl              |  15 ++
 src/bin/scripts/t/070_dropuser.pl              |  16 +++
 src/bin/scripts/t/080_pg_isready.pl            |  15 ++
 src/bin/scripts/t/090_reindexdb.pl             |  21 +++
 src/bin/scripts/t/091_reindexdb_all.pl         |  11 ++
 src/bin/scripts/t/100_vacuumdb.pl              |  17 +++
 src/bin/scripts/t/101_vacuumdb_all.pl          |   9 ++
 src/test/perl/TestLib.pm                       | 186 +++++++++++++++++++++++++
 39 files changed, 748 insertions(+), 4 deletions(-)
 create mode 100644 src/bin/initdb/t/001_initdb.pl
 create mode 100644 src/bin/pg_basebackup/t/010_pg_basebackup.pl
 create mode 100644 src/bin/pg_basebackup/t/020_pg_receivexlog.pl
 create mode 100644 src/bin/pg_config/t/001_pg_config.pl
 create mode 100644 src/bin/pg_controldata/t/001_pg_controldata.pl
 create mode 100644 src/bin/pg_ctl/t/001_start_stop.pl
 create mode 100644 src/bin/pg_ctl/t/002_status.pl
 create mode 100644 src/bin/scripts/t/010_clusterdb.pl
 create mode 100644 src/bin/scripts/t/011_clusterdb_all.pl
 create mode 100644 src/bin/scripts/t/020_createdb.pl
 create mode 100644 src/bin/scripts/t/030_createlang.pl
 create mode 100644 src/bin/scripts/t/040_createuser.pl
 create mode 100644 src/bin/scripts/t/050_dropdb.pl
 create mode 100644 src/bin/scripts/t/060_droplang.pl
 create mode 100644 src/bin/scripts/t/070_dropuser.pl
 create mode 100644 src/bin/scripts/t/080_pg_isready.pl
 create mode 100644 src/bin/scripts/t/090_reindexdb.pl
 create mode 100644 src/bin/scripts/t/091_reindexdb_all.pl
 create mode 100644 src/bin/scripts/t/100_vacuumdb.pl
 create mode 100644 src/bin/scripts/t/101_vacuumdb_all.pl
 create mode 100644 src/test/perl/TestLib.pm

diff --git a/GNUmakefile.in b/GNUmakefile.in
index a573880..69e0824 100644
--- a/GNUmakefile.in
+++ b/GNUmakefile.in
@@ -66,9 +66,9 @@ check check-tests: all
 check check-tests installcheck installcheck-parallel installcheck-tests:
 	$(MAKE) -C src/test/regress $@
 
-$(call recurse,check-world,src/test src/pl src/interfaces/ecpg contrib,check)
+$(call recurse,check-world,src/test src/pl src/interfaces/ecpg contrib src/bin,check)
 
-$(call recurse,installcheck-world,src/test src/pl src/interfaces/ecpg contrib,installcheck)
+$(call recurse,installcheck-world,src/test src/pl src/interfaces/ecpg contrib src/bin,installcheck)
 
 GNUmakefile: GNUmakefile.in $(top_builddir)/config.status
 	./config.status $@
diff --git a/configure b/configure
index 122ace7..e0dbdfe 100755
--- a/configure
+++ b/configure
@@ -627,6 +627,7 @@ ac_includes_default="\
 
 ac_subst_vars='LTLIBOBJS
 vpath_build
+PROVE
 OSX
 XSLTPROC
 COLLATEINDEX
@@ -14350,6 +14351,52 @@ fi
 done
 
 
+#
+# Check for test tools
+#
+for ac_prog in prove
+do
+  # Extract the first word of "$ac_prog", so it can be a program name with args.
+set dummy $ac_prog; ac_word=$2
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
+$as_echo_n "checking for $ac_word... " >&6; }
+if ${ac_cv_prog_PROVE+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  if test -n "$PROVE"; then
+  ac_cv_prog_PROVE="$PROVE" # Let the user override the test.
+else
+as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
+for as_dir in $PATH
+do
+  IFS=$as_save_IFS
+  test -z "$as_dir" && as_dir=.
+    for ac_exec_ext in '' $ac_executable_extensions; do
+  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
+    ac_cv_prog_PROVE="$ac_prog"
+    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
+    break 2
+  fi
+done
+  done
+IFS=$as_save_IFS
+
+fi
+fi
+PROVE=$ac_cv_prog_PROVE
+if test -n "$PROVE"; then
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PROVE" >&5
+$as_echo "$PROVE" >&6; }
+else
+  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
+$as_echo "no" >&6; }
+fi
+
+
+  test -n "$PROVE" && break
+done
+
+
 # Thread testing
 
 # We have to run the thread test near the end so we have all our symbols
diff --git a/configure.in b/configure.in
index 0f3e0cc..fc9c52f 100644
--- a/configure.in
+++ b/configure.in
@@ -1817,6 +1817,11 @@ PGAC_PATH_COLLATEINDEX
 AC_CHECK_PROGS(XSLTPROC, xsltproc)
 AC_CHECK_PROGS(OSX, [osx sgml2xml sx])
 
+#
+# Check for test tools
+#
+AC_CHECK_PROGS(PROVE, prove)
+
 # Thread testing
 
 # We have to run the thread test near the end so we have all our symbols
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index 673ec24..e808129 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -329,7 +329,8 @@ <title>Requirements</title>
       <application>Perl</> 5.8 or later is needed to build from a Git checkout,
       or if you changed the input files for any of the build steps that
       use Perl scripts.  If building on Windows you will need
-      <application>Perl</> in any case.
+      <application>Perl</> in any case.  <application>Perl</application> is
+      also required to run some test suites.
      </para>
     </listitem>
    </itemizedlist>
diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml
index 16b3621..aee049a 100644
--- a/doc/src/sgml/regress.sgml
+++ b/doc/src/sgml/regress.sgml
@@ -204,6 +204,12 @@ <title>Additional Test Suites</title>
      located in <filename>src/test/isolation</>.
     </para>
    </listitem>
+   <listitem>
+    <para>
+     Tests of client programs under <filename>src/bin</filename>.  See
+     also <xref linkend="regress-tap">.
+    </para>
+   </listitem>
   </itemizedlist>
 
   <para>
@@ -660,6 +666,28 @@ <title>Variant Comparison Files</title>
 
   </sect1>
 
+  <sect1 id="regress-tap">
+   <title>TAP Tests</title>
+
+   <para>
+    The client program tests under <filename>src/bin</filename> use the Perl
+    TAP tools and are run by <command>prove</command>.  You can pass
+    command-line options to <command>prove</command> by setting
+    the <command>make</command> variable <varname>PROVE_FLAGS</>, for example:
+<programlisting>
+make -C src/bin check PROVE_FLAGS='--reverse'
+</programlisting>
+    The default is <literal>--verbose</literal>.  See the manual page
+    of <command>prove</command> for more information.
+   </para>
+
+   <para>
+    The tests written in Perl require the Perl
+    module <literal>IPC::Run</literal>, otherwise most tests will be skipped.
+    This module is available from CPAN or an operating system package.
+   </para>
+  </sect1>
+
   <sect1 id="regress-coverage">
    <title>Test Coverage Examination</title>
 
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 209d1bd..5f1585c 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -1,5 +1,6 @@
 # -*-makefile-*-
 # src/Makefile.global.in
+# @configure_input@
 
 #------------------------------------------------------------------------------
 # All PostgreSQL makefiles include this file and use the variables it sets,
@@ -292,6 +293,22 @@ XGETTEXT = @XGETTEXT@
 GZIP	= gzip
 BZIP2	= bzip2
 
+# Testing
+
+PROVE = @PROVE@
+PG_PROVE_FLAGS = --ext='.pl' -I $(top_srcdir)/src/test/perl/
+PROVE_FLAGS = --verbose
+
+define prove_installcheck
+PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
+endef
+
+define prove_check
+$(MKDIR_P) tmp_check/log
+$(MAKE) -C $(top_builddir) DESTDIR=$(CURDIR)/tmp_check/install install >$(CURDIR)/tmp_check/log/install.log 2>&1
+PATH="$(CURDIR)/tmp_check/install$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
+endef
+
 # Installation.
 
 install_bin = @install_bin@
@@ -760,7 +777,7 @@ gcda_files := $(wildcard *.gcda)
 
 lcov.info: $(gcda_files)
 	rm -f *.gcov
-	$(if $^,$(LCOV) -d . -c -o $@ $(LCOVFLAGS))
+	$(if $^,$(LCOV) -d . -c -o $@ $(LCOVFLAGS) --gcov-tool $(GCOV))
 
 %.c.gcov: %.gcda | lcov.info
 	$(GCOV) -b -f -p -o . $(GCOVFLAGS) $*.c >$*.c.gcov.out
diff --git a/src/bin/initdb/.gitignore b/src/bin/initdb/.gitignore
index 0f74727..71a899f 100644
--- a/src/bin/initdb/.gitignore
+++ b/src/bin/initdb/.gitignore
@@ -2,3 +2,5 @@
 /localtime.c
 
 /initdb
+
+/tmp_check/
diff --git a/src/bin/initdb/Makefile b/src/bin/initdb/Makefile
index 1ece7ac..fb7d142 100644
--- a/src/bin/initdb/Makefile
+++ b/src/bin/initdb/Makefile
@@ -57,3 +57,10 @@ clean distclean maintainer-clean:
 
 # ensure that changes in datadir propagate into object file
 initdb.o: initdb.c $(top_builddir)/src/Makefile.global
+
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/initdb/t/001_initdb.pl b/src/bin/initdb/t/001_initdb.pl
new file mode 100644
index 0000000..ad6d74c
--- /dev/null
+++ b/src/bin/initdb/t/001_initdb.pl
@@ -0,0 +1,37 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 14;
+
+my $tempdir = TestLib::tempdir;
+
+program_help_ok('initdb');
+program_version_ok('initdb');
+program_options_handling_ok('initdb');
+
+command_ok(['initdb', "$tempdir/data"], 'basic initdb');
+command_fails(['initdb', "$tempdir/data"], 'existing data directory');
+command_ok(['initdb', '-N', "$tempdir/data2"], 'nosync');
+command_ok(['initdb', '-S', "$tempdir/data2"], 'sync only');
+command_fails(['initdb', '-S', "$tempdir/data3"], 'sync missing data directory');
+mkdir "$tempdir/data4" or BAIL_OUT($!);
+command_ok(['initdb', "$tempdir/data4"], 'existing empty data directory');
+
+system_or_bail "rm -rf $tempdir/*";
+
+command_ok(['initdb', "$tempdir/data", '-X', "$tempdir/pgxlog"], 'separate xlog directory');
+
+system_or_bail "rm -rf $tempdir/*";
+command_fails(['initdb', "$tempdir/data", '-X', 'pgxlog'], 'relative xlog directory not allowed');
+
+system_or_bail "rm -rf $tempdir/*";
+mkdir "$tempdir/pgxlog";
+command_ok(['initdb', "$tempdir/data", '-X', "$tempdir/pgxlog"], 'existing empty xlog directory');
+
+system_or_bail "rm -rf $tempdir/*";
+mkdir "$tempdir/pgxlog";
+mkdir "$tempdir/pgxlog/lost+found";
+command_fails(['initdb', "$tempdir/data", '-X', "$tempdir/pgxlog"], 'existing nonempty xlog directory');
+
+system_or_bail "rm -rf $tempdir/*";
+command_ok(['initdb', "$tempdir/data", '-T', 'german'], 'select default dictionary');
diff --git a/src/bin/pg_basebackup/.gitignore b/src/bin/pg_basebackup/.gitignore
index 1334a1f..17c1731 100644
--- a/src/bin/pg_basebackup/.gitignore
+++ b/src/bin/pg_basebackup/.gitignore
@@ -1,2 +1,4 @@
 /pg_basebackup
 /pg_receivexlog
+
+/tmp_check/
diff --git a/src/bin/pg_basebackup/Makefile b/src/bin/pg_basebackup/Makefile
index 17c91af..1b8d411 100644
--- a/src/bin/pg_basebackup/Makefile
+++ b/src/bin/pg_basebackup/Makefile
@@ -41,3 +41,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_basebackup$(X) pg_receivexlog$(X) $(OBJS) pg_basebackup.o pg_receivexlog.o
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
new file mode 100644
index 0000000..d825544
--- /dev/null
+++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl
@@ -0,0 +1,91 @@
+use strict;
+use warnings;
+use Cwd;
+use TestLib;
+use Test::More tests => 28;
+
+program_help_ok('pg_basebackup');
+program_version_ok('pg_basebackup');
+program_options_handling_ok('pg_basebackup');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+command_fails(['pg_basebackup'], 'pg_basebackup needs target directory specified');
+command_fails(['pg_basebackup', '-D', "$tempdir/backup"], 'pg_basebackup fails because of hba');
+
+open HBA, ">>$tempdir/pgdata/pg_hba.conf";
+print HBA "local replication all trust\n";
+print HBA "host replication all 127.0.0.1/32 trust\n";
+print HBA "host replication all ::1/128 trust\n";
+close HBA;
+system_or_bail 'pg_ctl', '-s', '-D', "$tempdir/pgdata", 'reload';
+
+command_fails(['pg_basebackup', '-D', "$tempdir/backup"], 'pg_basebackup fails because of WAL configuration');
+
+open CONF, ">>$tempdir/pgdata/postgresql.conf";
+print CONF "max_wal_senders = 10\n";
+print CONF "wal_level = archive\n";
+close CONF;
+restart_test_server;
+
+command_ok(['pg_basebackup', '-D', "$tempdir/backup"], 'pg_basebackup runs');
+ok(-f "$tempdir/backup/PG_VERSION", 'backup was created');
+
+command_ok(['pg_basebackup', '-D', "$tempdir/backup2", '--xlogdir', "$tempdir/xlog2"], 'separate xlog directory');
+ok(-f "$tempdir/backup2/PG_VERSION", 'backup was created');
+ok(-d "$tempdir/xlog2/", 'xlog directory was created');
+
+command_ok(['pg_basebackup', '-D', "$tempdir/tarbackup", '-Ft'], 'tar format');
+ok(-f "$tempdir/tarbackup/base.tar", 'backup tar was created');
+
+mkdir "$tempdir/tblspc1";
+psql 'postgres', "CREATE TABLESPACE tblspc1 LOCATION '$tempdir/tblspc1';";
+psql 'postgres', "CREATE TABLE test1 (a int) TABLESPACE tblspc1;";
+command_ok(['pg_basebackup', '-D', "$tempdir/tarbackup2", '-Ft'], 'tar format with tablespaces');
+ok(-f "$tempdir/tarbackup2/base.tar", 'backup tar was created');
+my @tblspc_tars = glob "$tempdir/tarbackup2/[0-9]*.tar";
+note("tablespace tars are @tblspc_tars");
+is(scalar(@tblspc_tars), 1, 'one tablespace tar was created');
+
+command_fails(['pg_basebackup', '-D', "$tempdir/backup1", '-Fp'],
+			  'plain format with tablespaces fails without tablespace mapping');
+
+command_ok(['pg_basebackup', '-D', "$tempdir/backup1", '-Fp',
+			"-T$tempdir/tblspc1=$tempdir/tbackup/tblspc1"],
+		   'plain format with tablespaces succeeds with tablespace mapping');
+ok(-d "$tempdir/tbackup/tblspc1", 'tablespace was relocated');
+opendir(my $dh, "$tempdir/pgdata/pg_tblspc") or die;
+ok((grep { -l "$tempdir/backup1/pg_tblspc/$_" and readlink "$tempdir/backup1/pg_tblspc/$_" eq "$tempdir/tbackup/tblspc1" } readdir($dh)),
+   "tablespace symlink was updated");
+closedir $dh;
+
+mkdir "$tempdir/tbl=spc2";
+psql 'postgres', "DROP TABLE test1;";
+psql 'postgres', "DROP TABLESPACE tblspc1;";
+psql 'postgres', "CREATE TABLESPACE tblspc2 LOCATION '$tempdir/tbl=spc2';";
+command_ok(['pg_basebackup', '-D', "$tempdir/backup3", '-Fp',
+			"-T$tempdir/tbl\\=spc2=$tempdir/tbackup/tbl\\=spc2"],
+		   'mapping tablespace with = sign in path');
+ok(-d "$tempdir/tbackup/tbl=spc2", 'tablespace with = sign was relocated');
+
+psql 'postgres', "DROP TABLESPACE tblspc2;";
+
+command_fails(['pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp',
+			   "-T=/foo"],
+			  '-T with empty old directory fails');
+command_fails(['pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp',
+			   "-T/foo="],
+			  '-T with empty new directory fails');
+command_fails(['pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp',
+			   "-T/foo=/bar=/baz"],
+			  '-T with multiple = fails');
+command_fails(['pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp',
+			   "-Tfoo=/bar"],
+			  '-T with old directory not absolute fails');
+command_fails(['pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp',
+			   "-T/foo=bar"],
+			  '-T with new directory not absolute fails');
+command_fails(['pg_basebackup', '-D', "$tempdir/backup_foo", '-Fp',
+			   "-Tfoo"],
+			  '-T with invalid format fails');
diff --git a/src/bin/pg_basebackup/t/020_pg_receivexlog.pl b/src/bin/pg_basebackup/t/020_pg_receivexlog.pl
new file mode 100644
index 0000000..700ae04
--- /dev/null
+++ b/src/bin/pg_basebackup/t/020_pg_receivexlog.pl
@@ -0,0 +1,8 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 3;
+
+program_help_ok('pg_receivexlog');
+program_version_ok('pg_receivexlog');
+program_options_handling_ok('pg_receivexlog');
diff --git a/src/bin/pg_config/.gitignore b/src/bin/pg_config/.gitignore
index 169bc76..cc42247 100644
--- a/src/bin/pg_config/.gitignore
+++ b/src/bin/pg_config/.gitignore
@@ -1 +1,2 @@
 /pg_config
+/tmp_check/
diff --git a/src/bin/pg_config/Makefile b/src/bin/pg_config/Makefile
index 578a2a5..579dce6 100644
--- a/src/bin/pg_config/Makefile
+++ b/src/bin/pg_config/Makefile
@@ -47,3 +47,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_config$(X) $(OBJS)
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_config/t/001_pg_config.pl b/src/bin/pg_config/t/001_pg_config.pl
new file mode 100644
index 0000000..200f394
--- /dev/null
+++ b/src/bin/pg_config/t/001_pg_config.pl
@@ -0,0 +1,12 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 7;
+
+program_help_ok('pg_config');
+program_version_ok('pg_config');
+program_options_handling_ok('pg_config');
+command_like(['pg_config', '--bindir'], qr/bin/, 'pg_config single option');  # XXX might be wrong
+command_like(['pg_config', '--bindir', '--libdir'],  qr/bin.*\n.*lib/, 'pg_config two options');
+command_like(['pg_config', '--libdir', '--bindir'],  qr/lib.*\n.*bin/, 'pg_config two options different order');
+command_like(['pg_config'], qr/.*\n.*\n.*/, 'pg_config without options prints many lines');
diff --git a/src/bin/pg_controldata/.gitignore b/src/bin/pg_controldata/.gitignore
index eab0c28..051d71d 100644
--- a/src/bin/pg_controldata/.gitignore
+++ b/src/bin/pg_controldata/.gitignore
@@ -1 +1,2 @@
 /pg_controldata
+/tmp_check/
diff --git a/src/bin/pg_controldata/Makefile b/src/bin/pg_controldata/Makefile
index 8b5f340..5abe76c 100644
--- a/src/bin/pg_controldata/Makefile
+++ b/src/bin/pg_controldata/Makefile
@@ -33,3 +33,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_controldata$(X) $(OBJS)
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl
new file mode 100644
index 0000000..3ddab80
--- /dev/null
+++ b/src/bin/pg_controldata/t/001_pg_controldata.pl
@@ -0,0 +1,14 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+my $tempdir = TestLib::tempdir;
+
+program_help_ok('pg_controldata');
+program_version_ok('pg_controldata');
+program_options_handling_ok('pg_controldata');
+command_fails(['pg_controldata'], 'pg_controldata without arguments fails');
+command_fails(['pg_controldata', 'nonexistent'], 'pg_controldata with nonexistent directory fails');
+system_or_bail "initdb -D $tempdir/data -A trust";
+command_like(['pg_controldata', "$tempdir/data"], qr/checkpoint/, 'pg_controldata produces output');
diff --git a/src/bin/pg_ctl/.gitignore b/src/bin/pg_ctl/.gitignore
index c90c103..73ab4ed 100644
--- a/src/bin/pg_ctl/.gitignore
+++ b/src/bin/pg_ctl/.gitignore
@@ -1 +1,2 @@
 /pg_ctl
+/tmp_check/
diff --git a/src/bin/pg_ctl/Makefile b/src/bin/pg_ctl/Makefile
index cbc1638..a9a0366 100644
--- a/src/bin/pg_ctl/Makefile
+++ b/src/bin/pg_ctl/Makefile
@@ -36,3 +36,9 @@ uninstall:
 
 clean distclean maintainer-clean:
 	rm -f pg_ctl$(X) $(OBJS)
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/pg_ctl/t/001_start_stop.pl b/src/bin/pg_ctl/t/001_start_stop.pl
new file mode 100644
index 0000000..f0184e0
--- /dev/null
+++ b/src/bin/pg_ctl/t/001_start_stop.pl
@@ -0,0 +1,25 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 10;
+
+my $tempdir = TestLib::tempdir;
+
+program_help_ok('pg_ctl');
+program_version_ok('pg_ctl');
+program_options_handling_ok('pg_ctl');
+
+command_ok(['pg_ctl', 'initdb', '-D', "$tempdir/data"], 'pg_ctl initdb');
+open CONF, ">>$tempdir/data/postgresql.conf";
+print CONF "listen_addresses = ''\n";
+print CONF "unix_socket_directories = '$tempdir'\n";
+close CONF;
+command_ok(['pg_ctl', 'start', '-D', "$tempdir/data", '-w'], 'pg_ctl start -w');
+command_ok(['pg_ctl', 'start', '-D', "$tempdir/data", '-w'], 'second pg_ctl start succeeds');
+command_ok(['pg_ctl', 'stop', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'pg_ctl stop -w');
+command_fails(['pg_ctl', 'stop', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'second pg_ctl stop fails');
+
+command_ok(['pg_ctl', 'restart', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'pg_ctl restart with server not running');
+command_ok(['pg_ctl', 'restart', '-D', "$tempdir/data", '-w', '-m', 'fast'], 'pg_ctl restart with server running');
+
+system_or_bail 'pg_ctl', 'stop', '-D', "$tempdir/data", '-m', 'fast';
diff --git a/src/bin/pg_ctl/t/002_status.pl b/src/bin/pg_ctl/t/002_status.pl
new file mode 100644
index 0000000..9ce5d65
--- /dev/null
+++ b/src/bin/pg_ctl/t/002_status.pl
@@ -0,0 +1,19 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 2;
+
+my $tempdir = TestLib::tempdir;
+
+system_or_bail "initdb -D $tempdir/data -A trust";
+open CONF, ">>$tempdir/data/postgresql.conf";
+print CONF "listen_addresses = ''\n";
+print CONF "unix_socket_directories = '$tempdir'\n";
+close CONF;
+
+command_exit_is(['pg_ctl', 'status', '-D', "$tempdir/data"], 3, 'pg_ctl status with server not running');
+
+system_or_bail 'pg_ctl', '-s', '-l', "$tempdir/logfile", '-D', "$tempdir/data", '-w', 'start';
+command_exit_is(['pg_ctl', 'status', '-D', "$tempdir/data"], 0, 'pg_ctl status with server running');
+
+system_or_bail 'pg_ctl', '-s', 'stop', '-D', "$tempdir/data", '-m', 'fast';
diff --git a/src/bin/scripts/.gitignore b/src/bin/scripts/.gitignore
index 0b9b786..1056b28 100644
--- a/src/bin/scripts/.gitignore
+++ b/src/bin/scripts/.gitignore
@@ -14,3 +14,5 @@
 /kwlookup.c
 /mbprint.c
 /print.c
+
+/tmp_check/
diff --git a/src/bin/scripts/Makefile b/src/bin/scripts/Makefile
index b5d9207..de0e11f 100644
--- a/src/bin/scripts/Makefile
+++ b/src/bin/scripts/Makefile
@@ -68,3 +68,10 @@ clean distclean maintainer-clean:
 	rm -f $(addsuffix $(X), $(PROGRAMS)) $(addsuffix .o, $(PROGRAMS))
 	rm -f common.o dumputils.o kwlookup.o keywords.o print.o mbprint.o $(WIN32RES)
 	rm -f dumputils.c print.c mbprint.c kwlookup.c keywords.c
+
+
+check: all
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
diff --git a/src/bin/scripts/t/010_clusterdb.pl b/src/bin/scripts/t/010_clusterdb.pl
new file mode 100644
index 0000000..371b2dd
--- /dev/null
+++ b/src/bin/scripts/t/010_clusterdb.pl
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+program_help_ok('clusterdb');
+program_version_ok('clusterdb');
+program_options_handling_ok('clusterdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['clusterdb', 'postgres'], qr/statement: CLUSTER;/, 'SQL CLUSTER run');
+
+command_fails(['clusterdb', '-t', 'nonexistent', 'postgres'], 'fails with nonexistent table');
+
+psql 'postgres', 'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a); CLUSTER test1 USING test1x';
+issues_sql_like(['clusterdb', 'postgres', '-t', 'test1'], qr/statement: CLUSTER test1;/, 'cluster specific table');
diff --git a/src/bin/scripts/t/011_clusterdb_all.pl b/src/bin/scripts/t/011_clusterdb_all.pl
new file mode 100644
index 0000000..304c4be
--- /dev/null
+++ b/src/bin/scripts/t/011_clusterdb_all.pl
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 1;
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['clusterdb', '-a'], qr/statement: CLUSTER.*statement: CLUSTER/s, 'cluster all databases');
diff --git a/src/bin/scripts/t/020_createdb.pl b/src/bin/scripts/t/020_createdb.pl
new file mode 100644
index 0000000..8b82a2b
--- /dev/null
+++ b/src/bin/scripts/t/020_createdb.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+program_help_ok('createdb');
+program_version_ok('createdb');
+program_options_handling_ok('createdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['createdb', 'foobar1'], qr/statement: CREATE DATABASE foobar1/, 'SQL CREATE DATABASE run');
+issues_sql_like(['createdb', 'foobar2', '-l', 'C', '-E', 'LATIN1', '-T', 'template0'], qr/statement: CREATE DATABASE foobar2 ENCODING 'LATIN1'/, 'create database with encoding');
+
+command_fails(['createdb', 'foobar1'], 'fails if database already exists');
diff --git a/src/bin/scripts/t/030_createlang.pl b/src/bin/scripts/t/030_createlang.pl
new file mode 100644
index 0000000..9a87f4c
--- /dev/null
+++ b/src/bin/scripts/t/030_createlang.pl
@@ -0,0 +1,18 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 6;
+
+program_help_ok('createlang');
+program_version_ok('createlang');
+program_options_handling_ok('createlang');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+command_fails(['createlang', 'plpgsql', 'postgres'], 'fails if language already exists');
+
+psql 'postgres', 'DROP EXTENSION plpgsql';
+issues_sql_like(['createlang', 'plpgsql', 'postgres'], qr/statement: CREATE EXTENSION "plpgsql"/, 'SQL CREATE EXTENSION run');
+
+command_like(['createlang', '--list', 'postgres'], qr/plpgsql/, 'list output');
diff --git a/src/bin/scripts/t/040_createuser.pl b/src/bin/scripts/t/040_createuser.pl
new file mode 100644
index 0000000..922873a
--- /dev/null
+++ b/src/bin/scripts/t/040_createuser.pl
@@ -0,0 +1,26 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 8;
+
+program_help_ok('createuser');
+program_version_ok('createuser');
+program_options_handling_ok('createuser');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['createuser', 'user1'],
+				qr/statement: CREATE ROLE user1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT LOGIN;/,
+				'SQL CREATE USER run');
+issues_sql_like(['createuser', '-L', 'role1'],
+				qr/statement: CREATE ROLE role1 NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOLOGIN;/,
+				'create a non-login role');
+issues_sql_like(['createuser', '-r', 'user2'],
+				qr/statement: CREATE ROLE user2 NOSUPERUSER NOCREATEDB CREATEROLE INHERIT LOGIN;/,
+				'create a CREATEROLE user');
+issues_sql_like(['createuser', '-s', 'user3'],
+				qr/statement: CREATE ROLE user3 SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN;/,
+				'create a superuser');
+
+command_fails(['createuser', 'user1'], 'fails if role already exists');
diff --git a/src/bin/scripts/t/050_dropdb.pl b/src/bin/scripts/t/050_dropdb.pl
new file mode 100644
index 0000000..3662dd0
--- /dev/null
+++ b/src/bin/scripts/t/050_dropdb.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('dropdb');
+program_version_ok('dropdb');
+program_options_handling_ok('dropdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+psql 'postgres', 'CREATE DATABASE foobar1';
+issues_sql_like(['dropdb', 'foobar1'], qr/statement: DROP DATABASE foobar1/, 'SQL DROP DATABASE run');
+
+command_fails(['dropdb', 'nonexistent'], 'fails with nonexistent database');
diff --git a/src/bin/scripts/t/060_droplang.pl b/src/bin/scripts/t/060_droplang.pl
new file mode 100644
index 0000000..47cb48f
--- /dev/null
+++ b/src/bin/scripts/t/060_droplang.pl
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('droplang');
+program_version_ok('droplang');
+program_options_handling_ok('droplang');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['droplang', 'plpgsql', 'postgres'], qr/statement: DROP EXTENSION "plpgsql"/, 'SQL DROP EXTENSION run');
+
+command_fails(['droplang', 'nonexistent', 'postgres'], 'fails with nonexistent language');
diff --git a/src/bin/scripts/t/070_dropuser.pl b/src/bin/scripts/t/070_dropuser.pl
new file mode 100644
index 0000000..495636a
--- /dev/null
+++ b/src/bin/scripts/t/070_dropuser.pl
@@ -0,0 +1,16 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('dropuser');
+program_version_ok('dropuser');
+program_options_handling_ok('dropuser');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+psql 'postgres', 'CREATE ROLE foobar1';
+issues_sql_like(['dropuser', 'foobar1'], qr/statement: DROP ROLE foobar1/, 'SQL DROP ROLE run');
+
+command_fails(['dropuser', 'nonexistent'], 'fails with nonexistent user');
diff --git a/src/bin/scripts/t/080_pg_isready.pl b/src/bin/scripts/t/080_pg_isready.pl
new file mode 100644
index 0000000..03c3657
--- /dev/null
+++ b/src/bin/scripts/t/080_pg_isready.pl
@@ -0,0 +1,15 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 5;
+
+program_help_ok('pg_isready');
+program_version_ok('pg_isready');
+program_options_handling_ok('pg_isready');
+
+command_fails(['pg_isready'], 'fails with no server running');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+command_ok(['pg_isready'], 'succeeds with server running');
diff --git a/src/bin/scripts/t/090_reindexdb.pl b/src/bin/scripts/t/090_reindexdb.pl
new file mode 100644
index 0000000..18756e8
--- /dev/null
+++ b/src/bin/scripts/t/090_reindexdb.pl
@@ -0,0 +1,21 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 7;
+
+program_help_ok('reindexdb');
+program_version_ok('reindexdb');
+program_options_handling_ok('reindexdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+$ENV{PGOPTIONS} = '--client-min-messages=WARNING';
+
+issues_sql_like(['reindexdb', 'postgres'], qr/statement: REINDEX DATABASE postgres;/, 'SQL REINDEX run');
+
+psql 'postgres', 'CREATE TABLE test1 (a int); CREATE INDEX test1x ON test1 (a);';
+issues_sql_like(['reindexdb', 'postgres', '-t', 'test1'], qr/statement: REINDEX TABLE test1;/, 'reindex specific table');
+issues_sql_like(['reindexdb', 'postgres', '-i', 'test1x'], qr/statement: REINDEX INDEX test1x;/, 'reindex specific index');
+
+issues_sql_like(['reindexdb', 'postgres', '-s'], qr/statement: REINDEX SYSTEM postgres;/, 'reindex system tables');
diff --git a/src/bin/scripts/t/091_reindexdb_all.pl b/src/bin/scripts/t/091_reindexdb_all.pl
new file mode 100644
index 0000000..eee8ba8
--- /dev/null
+++ b/src/bin/scripts/t/091_reindexdb_all.pl
@@ -0,0 +1,11 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 1;
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+$ENV{PGOPTIONS} = '--client-min-messages=WARNING';
+
+issues_sql_like(['reindexdb', '-a'], qr/statement: REINDEX.*statement: REINDEX/s, 'reindex all databases');
diff --git a/src/bin/scripts/t/100_vacuumdb.pl b/src/bin/scripts/t/100_vacuumdb.pl
new file mode 100644
index 0000000..39f1cec
--- /dev/null
+++ b/src/bin/scripts/t/100_vacuumdb.pl
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 8;
+
+program_help_ok('vacuumdb');
+program_version_ok('vacuumdb');
+program_options_handling_ok('vacuumdb');
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['vacuumdb', 'postgres'], qr/statement: VACUUM;/, 'SQL VACUUM run');
+issues_sql_like(['vacuumdb', '-f', 'postgres'], qr/statement: VACUUM \(FULL\);/, 'vacuumdb -f');
+issues_sql_like(['vacuumdb', '-F', 'postgres'], qr/statement: VACUUM \(FREEZE\);/, 'vacuumdb -F');
+issues_sql_like(['vacuumdb', '-z', 'postgres'], qr/statement: VACUUM \(ANALYZE\);/, 'vacuumdb -z');
+issues_sql_like(['vacuumdb', '-Z', 'postgres'], qr/statement: ANALYZE;/, 'vacuumdb -z');
diff --git a/src/bin/scripts/t/101_vacuumdb_all.pl b/src/bin/scripts/t/101_vacuumdb_all.pl
new file mode 100644
index 0000000..b5779bc
--- /dev/null
+++ b/src/bin/scripts/t/101_vacuumdb_all.pl
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 1;
+
+my $tempdir = tempdir;
+start_test_server $tempdir;
+
+issues_sql_like(['vacuumdb', '-a'], qr/statement: VACUUM.*statement: VACUUM/s, 'vacuum all databases');
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
new file mode 100644
index 0000000..a5a239e
--- /dev/null
+++ b/src/test/perl/TestLib.pm
@@ -0,0 +1,186 @@
+package TestLib;
+
+use strict;
+use warnings;
+
+use Exporter 'import';
+our @EXPORT = qw(
+	tempdir
+	start_test_server
+	restart_test_server
+	psql
+	system_or_bail
+
+	command_ok
+	command_fails
+	command_exit_is
+	program_help_ok
+	program_version_ok
+	program_options_handling_ok
+	command_like
+	issues_sql_like
+);
+
+use Cwd;
+use File::Spec;
+use File::Temp ();
+use Test::More;
+BEGIN {
+	eval {
+		require IPC::Run;
+		import IPC::Run qw(run start);
+		1;
+	} or do {
+		plan skip_all => "IPC::Run not available";
+	}
+}
+
+delete $ENV{PGCONNECT_TIMEOUT};
+delete $ENV{PGDATA};
+delete $ENV{PGDATABASE};
+delete $ENV{PGHOSTADDR};
+delete $ENV{PGREQUIRESSL};
+delete $ENV{PGSERVICE};
+delete $ENV{PGSSLMODE};
+delete $ENV{PGUSER};
+
+if (!$ENV{PGPORT}) {
+	$ENV{PGPORT} = 65432;
+}
+
+$ENV{PGPORT} = int($ENV{PGPORT}) % 65536;
+
+
+#
+# Helper functions
+#
+
+
+sub tempdir {
+	return File::Temp::tempdir('testXXXX', DIR => cwd(), CLEANUP => 1);
+}
+
+my ($test_server_datadir, $test_server_logfile);
+
+sub start_test_server {
+	my ($tempdir) = @_;
+	my $ret;
+
+	system "initdb -D $tempdir/pgdata -A trust -N >/dev/null";
+	$ret = system 'pg_ctl', '-D', "$tempdir/pgdata", '-s', '-w', '-l', "$tempdir/logfile", '-o', "--fsync=off -k $tempdir --listen-addresses='' --log-statement=all", 'start';
+
+	if ($ret != 0) {
+		system('cat', "$tempdir/logfile");
+		BAIL_OUT("pg_ctl failed");
+	}
+
+	$ENV{PGHOST} = $tempdir;
+	$test_server_datadir = "$tempdir/pgdata";
+	$test_server_logfile = "$tempdir/logfile";
+}
+
+sub restart_test_server {
+	system 'pg_ctl', '-s', '-D', $test_server_datadir, '-w', '-l', $test_server_logfile, 'restart';
+}
+
+END {
+	if ($test_server_datadir) {
+		system 'pg_ctl', '-D', $test_server_datadir, '-s', '-w', '-m', 'immediate', 'stop';
+	}
+}
+
+sub psql {
+	my ($dbname, $sql) = @_;
+	run ['psql', '-X', '-q', '-d', $dbname, '-f', '-'], '<', \$sql or die;
+}
+
+sub system_or_bail {
+	system(@_) == 0 or BAIL_OUT("system @_ failed: $?");
+}
+
+
+#
+# Test functions
+#
+
+
+sub command_ok {
+	my ($cmd, $test_name) = @_;
+	#my $result = run $cmd, '>', File::Spec->devnull(), '2>', File::Spec->devnull();
+	my $result = run $cmd;
+	ok($result, $test_name);
+}
+
+sub command_fails {
+	my ($cmd, $test_name) = @_;
+	my $result = run $cmd, '>', File::Spec->devnull(), '2>', File::Spec->devnull();
+	ok(!$result, $test_name);
+}
+
+sub command_exit_is {
+	my ($cmd, $expected, $test_name) = @_;
+	my $h = start $cmd, '>', File::Spec->devnull(), '2>', File::Spec->devnull();
+	$h->finish();
+	is($h->result(0), $expected, $test_name);
+}
+
+sub program_help_ok {
+	my ($cmd) = @_;
+	subtest "$cmd --help" => sub {
+		plan tests => 3;
+		my ($stdout, $stderr);
+		my $result = run [$cmd, '--help'], '>', \$stdout, '2>', \$stderr;
+		ok($result, "$cmd --help exit code 0");
+		isnt($stdout, '', "$cmd --help goes to stdout");
+		is($stderr, '', "$cmd --help nothing to stderr");
+	};
+}
+
+sub program_version_ok {
+	my ($cmd) = @_;
+	subtest "$cmd --version" => sub {
+		plan tests => 3;
+		my ($stdout, $stderr);
+		my $result = run [$cmd, '--version'], '>', \$stdout, '2>', \$stderr;
+		ok($result, "$cmd --version exit code 0");
+		isnt($stdout, '', "$cmd --version goes to stdout");
+		is($stderr, '', "$cmd --version nothing to stderr");
+	};
+}
+
+sub program_options_handling_ok {
+	my ($cmd) = @_;
+	subtest "$cmd options handling" => sub {
+		plan tests => 2;
+		my ($stdout, $stderr);
+		my $result = run [$cmd, '--not-a-valid-option'], '>', \$stdout, '2>', \$stderr;
+		ok(!$result, "$cmd with invalid option nonzero exit code");
+		isnt($stderr, '', "$cmd with invalid option prints error message");
+	};
+}
+
+sub command_like {
+	my ($cmd, $expected_stdout, $test_name) = @_;
+	subtest $test_name => sub {
+		plan tests => 3;
+		my ($stdout, $stderr);
+		my $result = run $cmd, '>', \$stdout, '2>', \$stderr;
+		ok($result, "@$cmd exit code 0");
+		is($stderr, '', "@$cmd no stderr");
+		like($stdout, $expected_stdout, "$test_name: matches");
+	};
+}
+
+sub issues_sql_like {
+	my ($cmd, $expected_sql, $test_name) = @_;
+	subtest $test_name => sub {
+		plan tests => 2;
+		truncate $test_server_logfile, 0;
+		my $result = run $cmd;
+		ok($result, "@$cmd exit code 0");
+		my $log = `cat $test_server_logfile`;
+		like($log, $expected_sql, "$test_name: SQL found in server log");
+	};
+}
+
+1;
-- 
1.9.0

#7Andres Freund
andres@2ndquadrant.com
In reply to: Peter Eisentraut (#6)
Re: tests for client programs

Hi,

I personally would very much like to get this patch commited. It doesn't
have much risk in destabilizing stuff, rather the contrary.

Peter, what's you opinion about the current state?

On 2014-02-27 21:44:48 -0500, Peter Eisentraut wrote:

diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml
index 16b3621..aee049a 100644
--- a/doc/src/sgml/regress.sgml
+++ b/doc/src/sgml/regress.sgml
@@ -204,6 +204,12 @@ <title>Additional Test Suites</title>
located in <filename>src/test/isolation</>.
</para>
</listitem>
+   <listitem>
+    <para>
+     Tests of client programs under <filename>src/bin</filename>.  See
+     also <xref linkend="regress-tap">.
+    </para>
+   </listitem>
</itemizedlist>

<para>
@@ -660,6 +666,28 @@ <title>Variant Comparison Files</title>

</sect1>

+  <sect1 id="regress-tap">
+   <title>TAP Tests</title>
+
+   <para>
+    The client program tests under <filename>src/bin</filename> use the Perl
+    TAP tools and are run by <command>prove</command>.  You can pass
+    command-line options to <command>prove</command> by setting
+    the <command>make</command> variable <varname>PROVE_FLAGS</>, for example:
+<programlisting>
+make -C src/bin check PROVE_FLAGS='--reverse'
+</programlisting>
+    The default is <literal>--verbose</literal>.  See the manual page
+    of <command>prove</command> for more information.
+   </para>
+
+   <para>
+    The tests written in Perl require the Perl
+    module <literal>IPC::Run</literal>, otherwise most tests will be skipped.
+    This module is available from CPAN or an operating system package.
+   </para>
+  </sect1>
+

There's actually also some binaries in /contrib, so maybe phrase this a
bit more generally?

<sect1 id="regress-coverage">

lcov.info: $(gcda_files)
rm -f *.gcov
-	$(if $^,$(LCOV) -d . -c -o $@ $(LCOVFLAGS))
+	$(if $^,$(LCOV) -d . -c -o $@ $(LCOVFLAGS) --gcov-tool $(GCOV))

Looks unrelated, but whatever.

+open HBA, ">>$tempdir/pgdata/pg_hba.conf";
+print HBA "local replication all trust\n";
+print HBA "host replication all 127.0.0.1/32 trust\n";
+print HBA "host replication all ::1/128 trust\n";
+close HBA;

Given the recent make check security discussions, this doesn't seem like
a good idea...

+issues_sql_like(['createdb', 'foobar1'], qr/statement: CREATE DATABASE foobar1/, 'SQL CREATE DATABASE run');
+issues_sql_like(['createdb', 'foobar2', '-l', 'C', '-E', 'LATIN1', '-T', 'template0'], qr/statement: CREATE DATABASE foobar2 ENCODING 'LATIN1'/, 'create database with encoding');

Hm. Are all platforms guaranteed to provide latin1?

diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
+if (!$ENV{PGPORT}) {
+	$ENV{PGPORT} = 65432;
+}
+
+$ENV{PGPORT} = int($ENV{PGPORT}) % 65536;

Hm. I think this should use logical similar to what pg_regress is using,
namely test a few ports.

+sub start_test_server {
+	my ($tempdir) = @_;
+	my $ret;
+
+	system "initdb -D $tempdir/pgdata -A trust -N >/dev/null";
+	$ret = system 'pg_ctl', '-D', "$tempdir/pgdata", '-s', '-w', '-l', "$tempdir/logfile", '-o', "--fsync=off -k $tempdir --listen-addresses='' --log-statement=all", 'start';
+
+	if ($ret != 0) {
+		system('cat', "$tempdir/logfile");
+		BAIL_OUT("pg_ctl failed");
+	}
+
+	$ENV{PGHOST} = $tempdir;
+	$test_server_datadir = "$tempdir/pgdata";
+	$test_server_logfile = "$tempdir/logfile";
+}

Should stuff like --fsync-off, -k, really be on by default?

I think the code to massage pg_hba.conf should also be here, there'll be
a fair number of tests that need it.

Some questions:
* I haven't looked very careful, but does this set PATH correctly to
pick up programs?
* What does installcheck mean for this?
* I think there should be support for contrib modules to use this
automatically, without overwriting makefile targets.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Peter Eisentraut
peter_e@gmx.net
In reply to: Andres Freund (#7)
Re: tests for client programs

On 4/4/14, 10:44 AM, Andres Freund wrote:

I personally would very much like to get this patch commited. It doesn't
have much risk in destabilizing stuff, rather the contrary.

Peter, what's you opinion about the current state?

I opine it's committed. ;-)

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#7)
Re: tests for client programs

On 2014-04-04 16:44:46 +0200, Andres Freund wrote:

On 2014-02-27 21:44:48 -0500, Peter Eisentraut wrote:

+open HBA, ">>$tempdir/pgdata/pg_hba.conf";
+print HBA "local replication all trust\n";
+print HBA "host replication all 127.0.0.1/32 trust\n";
+print HBA "host replication all ::1/128 trust\n";
+close HBA;

Given the recent make check security discussions, this doesn't seem like
a good idea...

+issues_sql_like(['createdb', 'foobar1'], qr/statement: CREATE DATABASE foobar1/, 'SQL CREATE DATABASE run');
+issues_sql_like(['createdb', 'foobar2', '-l', 'C', '-E', 'LATIN1', '-T', 'template0'], qr/statement: CREATE DATABASE foobar2 ENCODING 'LATIN1'/, 'create database with encoding');

Hm. Are all platforms guaranteed to provide latin1?

diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
+if (!$ENV{PGPORT}) {
+	$ENV{PGPORT} = 65432;
+}
+
+$ENV{PGPORT} = int($ENV{PGPORT}) % 65536;

Hm. I think this should use logical similar to what pg_regress is using,
namely test a few ports.

+sub start_test_server {
+	my ($tempdir) = @_;
+	my $ret;
+
+	system "initdb -D $tempdir/pgdata -A trust -N >/dev/null";
+	$ret = system 'pg_ctl', '-D', "$tempdir/pgdata", '-s', '-w', '-l', "$tempdir/logfile", '-o', "--fsync=off -k $tempdir --listen-addresses='' --log-statement=all", 'start';
+
+	if ($ret != 0) {
+		system('cat', "$tempdir/logfile");
+		BAIL_OUT("pg_ctl failed");
+	}
+
+	$ENV{PGHOST} = $tempdir;
+	$test_server_datadir = "$tempdir/pgdata";
+	$test_server_logfile = "$tempdir/logfile";
+}

Should stuff like --fsync-off, -k, really be on by default?

I think the code to massage pg_hba.conf should also be here, there'll be
a fair number of tests that need it.

The issues here don't seem to have been addressed in the commit. At
least the latin1 thing should be fixed.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#10Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#9)
Re: tests for client programs

On 2014-04-30 18:09:15 +0200, Andres Freund wrote:

The issues here don't seem to have been addressed in the commit. At
least the latin1 thing should be fixed.

As an additional issue it currently doesn't seem to work in VPATH
builds. That's imo a must fix.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Peter Eisentraut
peter_e@gmx.net
In reply to: Andres Freund (#9)
Re: tests for client programs

On Wed, 2014-04-30 at 18:09 +0200, Andres Freund wrote:

On 2014-04-04 16:44:46 +0200, Andres Freund wrote:

On 2014-02-27 21:44:48 -0500, Peter Eisentraut wrote:

+open HBA, ">>$tempdir/pgdata/pg_hba.conf";
+print HBA "local replication all trust\n";
+print HBA "host replication all 127.0.0.1/32 trust\n";
+print HBA "host replication all ::1/128 trust\n";
+close HBA;

Given the recent make check security discussions, this doesn't seem like
a good idea...

The socket file for the test server instance is in a private directory,
so that should be safe enough.

+issues_sql_like(['createdb', 'foobar1'], qr/statement: CREATE DATABASE foobar1/, 'SQL CREATE DATABASE run');
+issues_sql_like(['createdb', 'foobar2', '-l', 'C', '-E', 'LATIN1', '-T', 'template0'], qr/statement: CREATE DATABASE foobar2 ENCODING 'LATIN1'/, 'create database with encoding');

Hm. Are all platforms guaranteed to provide latin1?

Yes.

diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
+if (!$ENV{PGPORT}) {
+	$ENV{PGPORT} = 65432;
+}
+
+$ENV{PGPORT} = int($ENV{PGPORT}) % 65536;

Hm. I think this should use logical similar to what pg_regress is using,
namely test a few ports.

That could be improved in the future.

+sub start_test_server {
+	my ($tempdir) = @_;
+	my $ret;
+
+	system "initdb -D $tempdir/pgdata -A trust -N >/dev/null";
+	$ret = system 'pg_ctl', '-D', "$tempdir/pgdata", '-s', '-w', '-l', "$tempdir/logfile", '-o', "--fsync=off -k $tempdir --listen-addresses='' --log-statement=all", 'start';
+
+	if ($ret != 0) {
+		system('cat', "$tempdir/logfile");
+		BAIL_OUT("pg_ctl failed");
+	}
+
+	$ENV{PGHOST} = $tempdir;
+	$test_server_datadir = "$tempdir/pgdata";
+	$test_server_logfile = "$tempdir/logfile";
+}

Should stuff like --fsync-off, -k, really be on by default?

-k is to set the socket directory. You might be thinking of initdb -k?

I think the code to massage pg_hba.conf should also be here, there'll be
a fair number of tests that need it.

More refactoring is always possible as needs arise.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#12Andres Freund
andres@2ndquadrant.com
In reply to: Peter Eisentraut (#11)
Re: tests for client programs

On 2014-05-06 20:44:56 -0400, Peter Eisentraut wrote:

On Wed, 2014-04-30 at 18:09 +0200, Andres Freund wrote:

On 2014-04-04 16:44:46 +0200, Andres Freund wrote:

On 2014-02-27 21:44:48 -0500, Peter Eisentraut wrote:

+open HBA, ">>$tempdir/pgdata/pg_hba.conf";
+print HBA "local replication all trust\n";
+print HBA "host replication all 127.0.0.1/32 trust\n";
+print HBA "host replication all ::1/128 trust\n";
+close HBA;

Given the recent make check security discussions, this doesn't seem like
a good idea...

The socket file for the test server instance is in a private directory,
so that should be safe enough.

Well, you're explicitly configuring host connections... That's why I was
wondering.

diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
+if (!$ENV{PGPORT}) {
+	$ENV{PGPORT} = 65432;
+}
+
+$ENV{PGPORT} = int($ENV{PGPORT}) % 65536;

Hm. I think this should use logical similar to what pg_regress is using,
namely test a few ports.

That could be improved in the future.

Oddly enough you're overwriting it in Magefile.global.in's prove_check anyway.

Should stuff like --fsync-off, -k, really be on by default?

-k is to set the socket directory. You might be thinking of initdb -k?

Yes, sorry. Confused the line with initdb with the pg_ctl one.

I think the code to massage pg_hba.conf should also be here, there'll be
a fair number of tests that need it.

More refactoring is always possible as needs arise.

I was thinking of

+command_ok(['pg_ctl', 'initdb', '-D', "$tempdir/data"], 'pg_ctl initdb');
+open CONF, ">>$tempdir/data/postgresql.conf";
+print CONF "listen_addresses = ''\n";
+print CONF "unix_socket_directories = '$tempdir'\n";
+close CONF;

Sorry, accidentally wrote hba.conf instead of postgresql.conf because I
was thinking about listen_addresses/authentication.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#13Andres Freund
andres@2ndquadrant.com
In reply to: Andres Freund (#10)
Re: tests for client programs

On 2014-04-30 18:17:54 +0200, Andres Freund wrote:

On 2014-04-30 18:09:15 +0200, Andres Freund wrote:

The issues here don't seem to have been addressed in the commit. At
least the latin1 thing should be fixed.

As an additional issue it currently doesn't seem to work in VPATH
builds. That's imo a must fix.

A "cd $(srcdir) && .." in prove_installcheck and prove_check seems to do
the trick.

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#14Peter Eisentraut
peter_e@gmx.net
In reply to: Andres Freund (#13)
1 attachment(s)
Re: tests for client programs

On Wed, 2014-05-07 at 03:08 +0200, Andres Freund wrote:

As an additional issue it currently doesn't seem to work in VPATH
builds. That's imo a must fix.

A "cd $(srcdir) && .." in prove_installcheck and prove_check seems to do
the trick.

Here is my proposed patch for this.

Attachments:

tap-test-vpath.patchtext/x-patch; charset=UTF-8; name=tap-test-vpath.patchDownload
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index 14119a1..93be859 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -301,13 +301,13 @@ PG_PROVE_FLAGS = --ext='.pl' -I $(top_srcdir)/src/test/perl/
 PROVE_FLAGS = --verbose
 
 define prove_installcheck
-PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
+cd $(srcdir) && CURDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
 endef
 
 define prove_check
 $(MKDIR_P) tmp_check/log
 $(MAKE) -C $(top_builddir) DESTDIR=$(CURDIR)/tmp_check/install install >$(CURDIR)/tmp_check/log/install.log 2>&1
-PATH="$(CURDIR)/tmp_check/install$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
+cd $(srcdir) && CURDIR='$(CURDIR)' PATH="$(CURDIR)/tmp_check/install$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS)
 endef
 
 # Installation.
diff --git a/src/test/perl/TestLib.pm b/src/test/perl/TestLib.pm
index 8a31110..78622fa 100644
--- a/src/test/perl/TestLib.pm
+++ b/src/test/perl/TestLib.pm
@@ -62,7 +62,7 @@ $ENV{PGPORT} = int($ENV{PGPORT}) % 65536;
 
 sub tempdir
 {
-	return File::Temp::tempdir('testXXXX', DIR => cwd(), CLEANUP => 1);
+	return File::Temp::tempdir('testXXXX', DIR => $ENV{CURDIR} || cwd(), CLEANUP => 1);
 }
 
 my ($test_server_datadir, $test_server_logfile);
#15Tom Lane
tgl@sss.pgh.pa.us
In reply to: Peter Eisentraut (#14)
Re: tests for client programs

Peter Eisentraut <peter_e@gmx.net> writes:

On Wed, 2014-05-07 at 03:08 +0200, Andres Freund wrote:

As an additional issue it currently doesn't seem to work in VPATH
builds. That's imo a must fix.
A "cd $(srcdir) && .." in prove_installcheck and prove_check seems to do
the trick.

Here is my proposed patch for this.

BTW, my Salesforce colleagues were complaining to me that this stuff
doesn't work at all on older Perl versions; apparently IPC::Run has
changed significantly since Perl 5.8 or so. Can we do anything about
that?

They were also not too happy that the checks get skipped if IPC::Run isn't
installed (as it is not on stock RHEL, for instance). It'd be better if
we could avoid depending on stuff that isn't in a pretty-vanilla Perl
installation.

regards, tom lane

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#16Andres Freund
andres@2ndquadrant.com
In reply to: Peter Eisentraut (#14)
Re: tests for client programs

On 2014-06-04 20:40:40 -0400, Peter Eisentraut wrote:

On Wed, 2014-05-07 at 03:08 +0200, Andres Freund wrote:

As an additional issue it currently doesn't seem to work in VPATH
builds. That's imo a must fix.

A "cd $(srcdir) && .." in prove_installcheck and prove_check seems to do
the trick.

Here is my proposed patch for this.

Except that I'd rather named CURDIR REGRESSDIR or such this looks sane.

sub tempdir
{
-	return File::Temp::tempdir('testXXXX', DIR => cwd(), CLEANUP => 1);
+	return File::Temp::tempdir('testXXXX', DIR => $ENV{CURDIR} || cwd(), CLEANUP => 1);
}

Unrelated to this, but for me cleanup doesn't always seem to succeed?
Also could we name the directories tmp_testXXXX akin to tmp_check?

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#17Noah Misch
noah@leadboat.com
In reply to: Andres Freund (#16)
Re: tests for client programs

On Thu, Jun 05, 2014 at 10:57:03AM +0200, Andres Freund wrote:

On 2014-06-04 20:40:40 -0400, Peter Eisentraut wrote:

On Wed, 2014-05-07 at 03:08 +0200, Andres Freund wrote:

As an additional issue it currently doesn't seem to work in VPATH
builds. That's imo a must fix.

A "cd $(srcdir) && .." in prove_installcheck and prove_check seems to do
the trick.

Here is my proposed patch for this.

Except that I'd rather named CURDIR REGRESSDIR or such this looks sane.

sub tempdir
{
-	return File::Temp::tempdir('testXXXX', DIR => cwd(), CLEANUP => 1);
+	return File::Temp::tempdir('testXXXX', DIR => $ENV{CURDIR} || cwd(), CLEANUP => 1);
}

I recommend "TMPDIR => 1" instead of setting DIR. This temporary directory is
used for Unix sockets, so path length limitations will be a problem:

/messages/by-id/20140329172224.GA170273@tornado.leadboat.com

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#18Peter Eisentraut
peter_e@gmx.net
In reply to: Noah Misch (#17)
Re: tests for client programs

On Thu, 2014-06-05 at 21:57 -0400, Noah Misch wrote:

I recommend "TMPDIR => 1" instead of setting DIR.

I originally decided against doing that, because

1) I don't know if all systems would have enough space in their regular
temporary directory for the kinds of things we put there. Using the
build directory seems safer.

2) One "debugging" method is to set CLEANUP to false and then manually
inspect the data directory left behind. (In the future, this might be
exposed via a command-line option.) This would become more cumbersome
and error-prone if we used TMPDIR.

This temporary directory is
used for Unix sockets, so path length limitations will be a problem:

/messages/by-id/20140329172224.GA170273@tornado.leadboat.com

That, however, is a good argument for doing it the other way. Maybe we
need two temporary directories.

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#19Noah Misch
noah@leadboat.com
In reply to: Peter Eisentraut (#18)
Re: tests for client programs

On Mon, Jun 09, 2014 at 09:12:27PM -0400, Peter Eisentraut wrote:

On Thu, 2014-06-05 at 21:57 -0400, Noah Misch wrote:

I recommend "TMPDIR => 1" instead of setting DIR.

I originally decided against doing that, because

1) I don't know if all systems would have enough space in their regular
temporary directory for the kinds of things we put there. Using the
build directory seems safer.

2) One "debugging" method is to set CLEANUP to false and then manually
inspect the data directory left behind. (In the future, this might be
exposed via a command-line option.) This would become more cumbersome
and error-prone if we used TMPDIR.

This temporary directory is
used for Unix sockets, so path length limitations will be a problem:

/messages/by-id/20140329172224.GA170273@tornado.leadboat.com

That, however, is a good argument for doing it the other way. Maybe we
need two temporary directories.

Two temporary directories sounds fair, given those constraints.

--
Noah Misch
EnterpriseDB http://www.enterprisedb.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#20Andres Freund
andres@2ndquadrant.com
In reply to: Peter Eisentraut (#14)
Re: tests for client programs

Hi,

On 2014-06-04 20:40:40 -0400, Peter Eisentraut wrote:

On Wed, 2014-05-07 at 03:08 +0200, Andres Freund wrote:

As an additional issue it currently doesn't seem to work in VPATH
builds. That's imo a must fix.

A "cd $(srcdir) && .." in prove_installcheck and prove_check seems to do
the trick.

Here is my proposed patch for this.

Works here. The tmpdir thing is probably a separate patch...

Greetings,

Andres Freund

--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers