diff --git a/doc/FAQ_HPUX b/doc/FAQ_HPUX index d04aa92d0e8b2c8dcb3375b1aa416c1ab9ecc2e3..227adbefd59cea330c95dfad6b3d17f4067fdadf 100644 --- a/doc/FAQ_HPUX +++ b/doc/FAQ_HPUX @@ -3,7 +3,7 @@ Frequently Asked Questions (FAQ) for PostgreSQL 7.3 HP-UX Specific TO BE READ IN CONJUNCTION WITH THE NORMAL FAQ ======================================================= -last updated: $Date: 2004/09/02 17:46:24 $ +last updated: $Date: 2006/07/19 02:37:00 $ current maintainer: Tom Lane (tgl@sss.pgh.pa.us) original author: Tom Lane (tgl@sss.pgh.pa.us) @@ -84,19 +84,3 @@ low-order-digit differences in the geometry tests, which vary depending on which compiler and math library versions you use. Any other error is cause for suspicion. - -The parallel regression test script (gmake check) is known to lock up -on PA-RISC when run under HP's Bourne shells: /usr/bin/sh and -/sbin/sh. To fix this problem, you will need PHCO_30269 with its -dependent patch or successor patches: - - PHCO_30269 s700_800 cumulative sh-posix(1) patch - PHCO_29816 s700_800 rc(1M) scripts cumulative patch - -To work around this problem, use ksh to run the regression script: - - gmake SHELL=/bin/ksh check - -If you see that the tests have stopped making progress and only a shell -process is consuming CPU, kill the shell process and start over with the -above command. diff --git a/doc/src/sgml/regress.sgml b/doc/src/sgml/regress.sgml index 855c13a1ec61087f765a83eb81cbff9c949c3a2c..141eaaa0bf178789cf6af154f48fd7574fc3cd6a 100644 --- a/doc/src/sgml/regress.sgml +++ b/doc/src/sgml/regress.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/regress.sgml,v 1.52 2006/06/18 15:38:36 petere Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/regress.sgml,v 1.53 2006/07/19 02:37:00 tgl Exp $ --> <chapter id="regress"> <title id="regress-title">Regression Tests</title> @@ -110,19 +110,6 @@ gmake MAX_CONNECTIONS=10 check runs no more than ten tests concurrently. </para> - <para> - On some systems, the default Bourne-compatible shell - (<filename>/bin/sh</filename>) gets confused when it has to manage - too many child processes in parallel. This may cause the parallel - test run to lock up or fail. In such cases, specify a different - Bourne-compatible shell on the command line, for example: -<screen> -gmake SHELL=/bin/ksh check -</screen> - If no non-broken shell is available, you may be able to work around the - problem by limiting the number of connections, as shown above. - </para> - <para> To run the tests after installation<![%standalone-ignore;[ (see <xref linkend="installation">)]]>, initialize a data area and start the @@ -370,13 +357,10 @@ testname/platformpattern=comparisonfilename The test name is just the name of the particular regression test module. The platform pattern is a pattern in the style of the Unix tool <command>expr</> (that is, a regular expression with an implicit - <literal>^</literal> anchor - at the start). It is matched against the platform name as printed - by <command>config.guess</command> followed by - <literal>:gcc</literal> or <literal>:cc</literal>, depending on - whether you use the GNU compiler or the system's native compiler - (on systems where there is a difference). The comparison file - name is the base name of the substitute result comparison file. + <literal>^</literal> anchor at the start). It is matched against the + platform name as printed by <command>config.guess</command>. + The comparison file name is the base name of the substitute result + comparison file. </para> <para> diff --git a/src/makefiles/pgxs.mk b/src/makefiles/pgxs.mk index d2969e44407e8502953faf0cc59ab36ceb2e084a..a26622922bd043cf5a281e44aa62f49755ab63c1 100644 --- a/src/makefiles/pgxs.mk +++ b/src/makefiles/pgxs.mk @@ -1,6 +1,6 @@ # PGXS: PostgreSQL extensions makefile -# $PostgreSQL: pgsql/src/makefiles/pgxs.mk,v 1.7 2005/12/09 21:19:36 petere Exp $ +# $PostgreSQL: pgsql/src/makefiles/pgxs.mk,v 1.8 2006/07/19 02:37:00 tgl Exp $ # This file contains generic rules to build many kinds of simple # extension modules. You only need to set a few variables and include @@ -230,16 +230,16 @@ endif # VPATH .PHONY: submake submake: ifndef PGXS - $(MAKE) -C $(top_builddir)/src/test/regress pg_regress + $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X) endif # against installed postmaster installcheck: submake - $(SHELL) $(top_builddir)/src/test/regress/pg_regress $(REGRESS_OPTS) $(REGRESS) + $(top_builddir)/src/test/regress/pg_regress $(REGRESS_OPTS) $(REGRESS) # in-tree test doesn't work yet (no way to install my shared library) #check: all submake -# $(SHELL) $(top_builddir)/src/test/regress/pg_regress --temp-install \ +# $(top_builddir)/src/test/regress/pg_regress --temp-install \ # --top-builddir=$(top_builddir) $(REGRESS_OPTS) $(REGRESS) check: @echo "'make check' is not supported." diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile index 1831f9ddf8a7c4da056c95e2e5048d807b272722..73f36bd44ac9ca5cd6559b8ef75dde1a1ec7824d 100644 --- a/src/pl/plperl/GNUmakefile +++ b/src/pl/plperl/GNUmakefile @@ -1,5 +1,5 @@ # Makefile for PL/Perl -# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.26 2005/12/09 21:19:36 petere Exp $ +# $PostgreSQL: pgsql/src/pl/plperl/GNUmakefile,v 1.27 2006/07/19 02:37:00 tgl Exp $ subdir = src/pl/plperl top_builddir = ../../.. @@ -84,11 +84,11 @@ uninstall: rm -f '$(DESTDIR)$(pkglibdir)/plperl$(DLSUFFIX)' installcheck: submake - $(SHELL) $(top_builddir)/src/test/regress/pg_regress $(REGRESS_OPTS) $(REGRESS) + $(top_builddir)/src/test/regress/pg_regress $(REGRESS_OPTS) $(REGRESS) .PHONY: submake submake: - $(MAKE) -C $(top_builddir)/src/test/regress pg_regress + $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X) clean distclean maintainer-clean: clean-lib rm -f SPI.c $(OBJS) diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index df691cf8dfef41cf296b1363672ab67ed6be094b..5823e205dcd8b1b18a1d975e60cffe27e2043dfe 100644 --- a/src/pl/plpython/Makefile +++ b/src/pl/plpython/Makefile @@ -1,4 +1,4 @@ -# $PostgreSQL: pgsql/src/pl/plpython/Makefile,v 1.24 2005/12/09 21:19:36 petere Exp $ +# $PostgreSQL: pgsql/src/pl/plpython/Makefile,v 1.25 2006/07/19 02:37:00 tgl Exp $ subdir = src/pl/plpython top_builddir = ../../.. @@ -103,11 +103,11 @@ uninstall: rm -f '$(DESTDIR)$(pkglibdir)/plpython$(DLSUFFIX)' installcheck: submake - $(SHELL) $(top_builddir)/src/test/regress/pg_regress $(REGRESS_OPTS) $(REGRESS) + $(top_builddir)/src/test/regress/pg_regress $(REGRESS_OPTS) $(REGRESS) .PHONY: submake submake: - $(MAKE) -C $(top_builddir)/src/test/regress pg_regress + $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X) clean distclean maintainer-clean: clean-lib rm -f $(OBJS) diff --git a/src/pl/tcl/Makefile b/src/pl/tcl/Makefile index d953c4bff836bd48d1e1bcaa29b81c8b8b1bcf43..a474c71e2d91f9f60b9c20755dd98493a747ecc7 100644 --- a/src/pl/tcl/Makefile +++ b/src/pl/tcl/Makefile @@ -2,7 +2,7 @@ # # Makefile for the pltcl shared object # -# $PostgreSQL: pgsql/src/pl/tcl/Makefile,v 1.48 2005/12/09 21:19:36 petere Exp $ +# $PostgreSQL: pgsql/src/pl/tcl/Makefile,v 1.49 2006/07/19 02:37:00 tgl Exp $ # #------------------------------------------------------------------------- @@ -90,11 +90,11 @@ uninstall: $(MAKE) -C modules $@ installcheck: submake - $(SHELL) $(top_builddir)/src/test/regress/pg_regress $(REGRESS_OPTS) $(REGRESS) + $(top_builddir)/src/test/regress/pg_regress $(REGRESS_OPTS) $(REGRESS) .PHONY: submake submake: - $(MAKE) -C $(top_builddir)/src/test/regress pg_regress + $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X) else # TCL_SHARED_BUILD = 0 diff --git a/src/test/regress/GNUmakefile b/src/test/regress/GNUmakefile index 3a2b66740541cd7aed1887b8c64a958a2b55b225..a3e00a7f65bcb609713807287c4928423309e4a4 100644 --- a/src/test/regress/GNUmakefile +++ b/src/test/regress/GNUmakefile @@ -6,7 +6,7 @@ # Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group # Portions Copyright (c) 1994, Regents of the University of California # -# $PostgreSQL: pgsql/src/test/regress/GNUmakefile,v 1.57 2006/03/05 15:59:11 momjian Exp $ +# $PostgreSQL: pgsql/src/test/regress/GNUmakefile,v 1.58 2006/07/19 02:37:00 tgl Exp $ # #------------------------------------------------------------------------- @@ -34,32 +34,33 @@ ifdef NO_LOCALE NOLOCALE += --no-locale endif +# stuff to pass into build of pg_regress +EXTRADEFS = '-DPGBINDIR="$(bindir)"' \ + '-DLIBDIR="$(libdir)"' \ + '-DPGSHAREDIR="$(datadir)"' \ + '-DHOST_TUPLE="$(host_tuple)"' \ + '-DMAKEPROG="$(MAKE)"' + ## ## Prepare for tests ## # Build regression test driver -all: pg_regress +all: submake-libpgport pg_regress$(X) + +pg_regress$(X): pg_regress.o + $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LIBS) -o $@ -pg_regress: pg_regress.sh GNUmakefile $(top_builddir)/src/Makefile.global - sed -e 's,@bindir@,$(bindir),g' \ - -e 's,@libdir@,$(libdir),g' \ - -e 's,@pkglibdir@,$(pkglibdir),g' \ - -e 's,@datadir@,$(datadir),g' \ - -e 's/@VERSION@/$(VERSION)/g' \ - -e 's/@host_tuple@/$(host_tuple)/g' \ - -e 's,@GMAKE@,$(MAKE),g' \ - -e 's/@enable_shared@/$(enable_shared)/g' \ - -e 's/@GCC@/$(GCC)/g' \ - $< >$@ - chmod a+x $@ +# depend on Makefile.global to ensure that symbol changes propagate +pg_regress.o: pg_regress.c $(top_builddir)/src/Makefile.global + $(CC) $(CFLAGS) $(CPPFLAGS) $(EXTRADEFS) -c -o $@ $< -install: pg_regress - $(INSTALL_SCRIPT) pg_regress '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_regress' +install: pg_regress$(X) + $(INSTALL_PROGRAM) pg_regress$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_regress$(X)' uninstall: - rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_regress' + rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_regress$(X)' # Build dynamically-loaded object file for CREATE FUNCTION ... LANGUAGE C. @@ -143,17 +144,17 @@ all-spi: check: all -rm -rf ./testtablespace mkdir ./testtablespace - $(SHELL) ./pg_regress --temp-install --top-builddir=$(top_builddir) --temp-port=$(TEMP_PORT) --schedule=$(srcdir)/parallel_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(MAXCONNOPT) $(NOLOCALE) + ./pg_regress --temp-install=./tmp_check --top-builddir=$(top_builddir) --temp-port=$(TEMP_PORT) --schedule=$(srcdir)/parallel_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(MAXCONNOPT) $(NOLOCALE) installcheck: all -rm -rf ./testtablespace mkdir ./testtablespace - $(SHELL) ./pg_regress --schedule=$(srcdir)/serial_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(NOLOCALE) + ./pg_regress --schedule=$(srcdir)/serial_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(NOLOCALE) installcheck-parallel: all -rm -rf ./testtablespace mkdir ./testtablespace - $(SHELL) ./pg_regress --schedule=$(srcdir)/parallel_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(MAXCONNOPT) $(NOLOCALE) + ./pg_regress --schedule=$(srcdir)/parallel_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(MAXCONNOPT) $(NOLOCALE) # old interfaces follow... @@ -163,10 +164,10 @@ runtest: installcheck runtest-parallel: installcheck-parallel bigtest: - $(SHELL) ./pg_regress --schedule=$(srcdir)/serial_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(NOLOCALE) numeric_big + ./pg_regress --schedule=$(srcdir)/serial_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(NOLOCALE) numeric_big bigcheck: - $(SHELL) ./pg_regress --temp-install --top-builddir=$(top_builddir) --temp-port=$(TEMP_PORT) --schedule=$(srcdir)/parallel_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(MAXCONNOPT) $(NOLOCALE) numeric_big + ./pg_regress --temp-install=./tmp_check --top-builddir=$(top_builddir) --temp-port=$(TEMP_PORT) --schedule=$(srcdir)/parallel_schedule --multibyte=$(MULTIBYTE) --load-language=plpgsql $(MAXCONNOPT) $(NOLOCALE) numeric_big ## @@ -177,7 +178,7 @@ clean distclean maintainer-clean: clean-lib # things built by `all' target rm -f $(NAME)$(DLSUFFIX) $(OBJS) $(MAKE) -C $(contribdir)/spi clean - rm -f $(output_files) $(input_files) pg_regress + rm -f $(output_files) $(input_files) pg_regress.o pg_regress$(X) # things created by various check targets rm -rf testtablespace rm -rf results tmp_check log diff --git a/src/test/regress/Makefile b/src/test/regress/Makefile index fbb3148861da882b1755e72f26f56a0be6d1295a..ede55c17f17155b9e593bfcd9501ee83830cfee6 100644 --- a/src/test/regress/Makefile +++ b/src/test/regress/Makefile @@ -7,6 +7,6 @@ # GNU make uses a make file named "GNUmakefile" in preference to "Makefile" # if it exists. Postgres is shipped with a "GNUmakefile". -all install clean dep depend: +all install clean dep depend check installcheck: @echo "You must use GNU make to use Postgres. It may be installed" @echo "on your system with the name 'gmake'." diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c new file mode 100644 index 0000000000000000000000000000000000000000..d8f7c3e1d965700661d64aa7b11a8e891709d423 --- /dev/null +++ b/src/test/regress/pg_regress.c @@ -0,0 +1,1601 @@ +/*------------------------------------------------------------------------- + * + * pg_regress --- regression test driver + * + * This is a C implementation of the previous shell script for running + * the regression tests, and should be highly compatible with it. + * Initial author of C translation: Magnus Hagander + * + * This code is released under the terms of the PostgreSQL License. + * + * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * $PostgreSQL: pgsql/src/test/regress/pg_regress.c,v 1.1 2006/07/19 02:37:00 tgl Exp $ + * + *------------------------------------------------------------------------- + */ + +#include "postgres_fe.h" + +#include <ctype.h> +#include <sys/stat.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "getopt_long.h" + +#ifndef WIN32 +#define PID_TYPE pid_t +#define INVALID_PID (-1) +#else +#define PID_TYPE HANDLE +#define INVALID_PID INVALID_HANDLE_VALUE +#endif + + +/* simple list of strings */ +typedef struct _stringlist +{ + char *str; + struct _stringlist *next; +} _stringlist; + +/* for resultmap we need a list of pairs of strings */ +typedef struct _resultmap +{ + char *test; + char *resultfile; + struct _resultmap *next; +} _resultmap; + +/* + * Values inserted from Makefile. (It might seem tempting to get the paths + * via get_share_path() and friends, but that's not going to work because + * pg_regress is typically not executed from an installed bin directory.) + */ +static char *bindir = PGBINDIR; +static char *libdir = LIBDIR; +static char *datadir = PGSHAREDIR; +static char *host_platform = HOST_TUPLE; +static char *makeprog = MAKEPROG; + +/* currently we can use the same diff switches on all platforms */ +static const char *basic_diff_opts = "-w"; +static const char *pretty_diff_opts = "-w -C3"; + +/* options settable from command line */ +static char *dbname = "regression"; +static bool debug = false; +static char *inputdir = "."; +static char *outputdir = "."; +static _stringlist *loadlanguage = NULL; +static int max_connections = 0; +static char *encoding = NULL; +static _stringlist *schedulelist = NULL; +static _stringlist *extra_tests = NULL; +static char *temp_install = NULL; +static char *top_builddir = NULL; +static int temp_port = 65432; +static bool nolocale = false; +static char *hostname = NULL; +static int port = -1; +static char *user = NULL; + +/* internal variables */ +static const char *progname; +static char *logfilename; +static FILE *logfile; +static char *difffilename; + +static _resultmap *resultmap = NULL; + +static PID_TYPE postmaster_pid = INVALID_PID; +static bool postmaster_running = false; + +static int success_count = 0; +static int fail_count = 0; +static int fail_ignore_count = 0; + +static void +header(const char *fmt,...) +/* This extension allows gcc to check the format string for consistency with + the supplied arguments. */ +__attribute__((format(printf, 1, 2))); +static void +status(const char *fmt,...) +/* This extension allows gcc to check the format string for consistency with + the supplied arguments. */ +__attribute__((format(printf, 1, 2))); +static void +psql_command(const char *database, const char *query, ...) +/* This extension allows gcc to check the format string for consistency with + the supplied arguments. */ +__attribute__((format(printf, 2, 3))); + + +/* + * Add an item at the end of a stringlist. + */ +static void +add_stringlist_item(_stringlist **listhead, const char *str) +{ + _stringlist *newentry = malloc(sizeof(_stringlist)); + _stringlist *oldentry; + + newentry->str = strdup(str); + newentry->next = NULL; + if (*listhead == NULL) + *listhead = newentry; + else + { + for (oldentry = *listhead; oldentry->next; oldentry = oldentry->next) + /*skip*/; + oldentry->next = newentry; + } +} + +/* + * Print a progress banner on stdout. + */ +static void +header(const char *fmt,...) +{ + char tmp[64]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(tmp, sizeof(tmp), fmt, ap); + va_end(ap); + + fprintf(stdout, "============== %-38s ==============\n", tmp); + fflush(stdout); +} + +/* + * Print "doing something ..." --- supplied text should not end with newline + */ +static void +status(const char *fmt,...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + fflush(stdout); + va_end(ap); + + if (logfile) + { + va_start(ap, fmt); + vfprintf(logfile, fmt, ap); + va_end(ap); + } +} + +/* + * Done "doing something ..." + */ +static void +status_end(void) +{ + fprintf(stdout, "\n"); + fflush(stdout); + if (logfile) + fprintf(logfile, "\n"); +} + +/* + * shut down temp postmaster + */ +static void +stop_postmaster(void) +{ + if (postmaster_running) + { + /* We use pg_ctl to issue the kill and wait for stop */ + char buf[MAXPGPATH * 2]; + + snprintf(buf, sizeof(buf), + "\"%s/pg_ctl\" stop -D \"%s/data\" -s -m fast", + bindir, temp_install); + system(buf); /* ignore exit status */ + postmaster_running = false; + } +} + +/* + * Always exit through here, not through plain exit(), to ensure we make + * an effort to shut down a temp postmaster + */ +static void +exit_nicely(int code) +{ + stop_postmaster(); + exit(code); +} + +/* + * Check whether string matches pattern + * + * In the original shell script, this function was implemented using expr(1), + * which provides basic regular expressions restricted to match starting at + * the string start (in conventional regex terms, there's an implicit "^" + * at the start of the pattern --- but no implicit "$" at the end). + * + * For now, we only support "." and ".*" as non-literal metacharacters, + * because that's all that anyone has found use for in resultmap. This + * code could be extended if more functionality is needed. + */ +static bool +string_matches_pattern(const char *str, const char *pattern) +{ + while (*str && *pattern) + { + if (*pattern == '.' && pattern[1] == '*') + { + pattern += 2; + /* Trailing .* matches everything. */ + if (*pattern == '\0') + return true; + + /* + * Otherwise, scan for a text position at which we can match the + * rest of the pattern. + */ + while (*str) + { + /* + * Optimization to prevent most recursion: don't recurse + * unless first pattern char might match this text char. + */ + if (*str == *pattern || *pattern == '.') + { + if (string_matches_pattern(str, pattern)) + return true; + } + + str++; + } + + /* + * End of text with no match. + */ + return false; + } + else if (*pattern != '.' && *str != *pattern) + { + /* + * Not the single-character wildcard and no explicit match? Then + * time to quit... + */ + return false; + } + + str++; + pattern++; + } + + if (*pattern == '\0') + return true; /* end of pattern, so declare match */ + + /* End of input string. Do we have matching pattern remaining? */ + while (*pattern == '.' && pattern[1] == '*') + pattern += 2; + if (*pattern == '\0') + return true; /* end of pattern, so declare match */ + + return false; +} + +/* + * Scan resultmap file to find which platform-specific expected files to use. + * + * The format of each line of the file is + * testname/hostplatformpattern=substitutefile + * where the hostplatformpattern is evaluated per the rules of expr(1), + * namely, it is a standard regular expression with an implicit ^ at the start. + * (We currently support only a very limited subset of regular expressions, + * see string_matches_pattern() above.) What hostplatformpattern will be + * matched against is the config.guess output. (In the shell-script version, + * we also provided an indication of whether gcc or another compiler was in + * use, but that facility isn't used anymore.) + */ +static void +load_resultmap(void) +{ + char buf[MAXPGPATH]; + FILE *f; + + /* scan the file ... */ + snprintf(buf, sizeof(buf), "%s/resultmap", inputdir); + f = fopen(buf,"r"); + if (!f) + { + /* OK if it doesn't exist, else complain */ + if (errno == ENOENT) + return; + fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"), + progname, buf, strerror(errno)); + exit_nicely(2); + } + memset(buf, 0, sizeof(buf)); + while (fgets(buf, sizeof(buf)-1, f)) + { + char *platform; + char *expected; + int i; + + /* strip trailing whitespace, especially the newline */ + i = strlen(buf); + while (i > 0 && isspace((unsigned char) buf[i-1])) + buf[--i] = '\0'; + + /* parse out the line fields */ + platform = strchr(buf, '/'); + if (!platform) + { + fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"), + buf); + exit_nicely(2); + } + *platform++ = '\0'; + expected = strchr(platform, '='); + if (!expected) + { + fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"), + buf); + exit_nicely(2); + } + *expected++ = '\0'; + + /* + * if it's for current platform, save it in resultmap list. + * Note: by adding at the front of the list, we ensure that in + * ambiguous cases, the last match in the resultmap file is used. + * This mimics the behavior of the old shell script. + */ + if (string_matches_pattern(host_platform, platform)) + { + _resultmap *entry = malloc(sizeof(_resultmap)); + + entry->test = strdup(buf); + entry->resultfile = strdup(expected); + entry->next = resultmap; + resultmap = entry; + } + } + fclose(f); +} + +/* + * Handy subroutine for setting an environment variable "var" to "val" + */ +static void +doputenv(const char *var, const char *val) +{ + char *s = malloc(strlen(var)+strlen(val)+2); + + sprintf(s, "%s=%s", var, val); + putenv(s); +} + +/* + * Set the environment variable "pathname", prepending "addval" to its + * old value (if any). + */ +static void +add_to_path(const char *pathname, char separator, const char *addval) +{ + char *oldval = getenv(pathname); + char *newval; + + if (!oldval || !oldval[0]) + { + /* no previous value */ + newval = malloc(strlen(pathname) + strlen(addval) + 2); + sprintf(newval, "%s=%s", pathname, addval); + } + else + { + newval = malloc(strlen(pathname) + strlen(addval) + strlen(oldval) + 3); + sprintf(newval,"%s=%s%c%s",pathname,addval,separator,oldval); + } + putenv(newval); +} + +/* + * Prepare environment variables for running regression tests + */ +static void +initialize_environment(void) +{ + char *tmp; + + /* + * Clear out any non-C locale settings + */ + unsetenv("LC_COLLATE"); + unsetenv("LC_CTYPE"); + unsetenv("LC_MONETARY"); + unsetenv("LC_MESSAGES"); + unsetenv("LC_NUMERIC"); + unsetenv("LC_TIME"); + unsetenv("LC_ALL"); + unsetenv("LANG"); + unsetenv("LANGUAGE"); + /* On Windows the default locale may not be English, so force it */ +#if defined(WIN32) || defined(CYGWIN) + putenv("LANG=en"); +#endif + + /* + * Set multibyte as requested + */ + if (encoding) + doputenv("PGCLIENTENCODING", encoding); + else + unsetenv("PGCLIENTENCODING"); + + /* + * Set timezone and datestyle for datetime-related tests + */ + putenv("PGTZ=PST8PDT"); + putenv("PGDATESTYLE=Postgres, MDY"); + + if (temp_install) + { + /* + * Clear out any environment vars that might cause psql to connect + * to the wrong postmaster, or otherwise behave in nondefault ways. + * (Note we also use psql's -X switch consistently, so that ~/.psqlrc + * files won't mess things up.) Also, set PGPORT to the temp port, + * and set or unset PGHOST depending on whether we are using TCP or + * Unix sockets. + */ + unsetenv("PGDATABASE"); + unsetenv("PGUSER"); + unsetenv("PGSERVICE"); + unsetenv("PGSSLMODE"); + unsetenv("PGREQUIRESSL"); + unsetenv("PGCONNECT_TIMEOUT"); + unsetenv("PGDATA"); + if (hostname != NULL) + doputenv("PGHOST", hostname); + else + unsetenv("PGHOST"); + unsetenv("PGHOSTADDR"); + if (port != -1) + { + char s[16]; + + sprintf(s,"%d",port); + doputenv("PGPORT",s); + } + + /* + * Adjust path variables to point into the temp-install tree + */ + tmp = malloc(strlen(temp_install) + 32 + strlen(bindir)); + sprintf(tmp, "%s/install/%s", temp_install, bindir); + bindir = tmp; + + tmp = malloc(strlen(temp_install) + 32 + strlen(libdir)); + sprintf(tmp, "%s/install/%s", temp_install, libdir); + libdir = tmp; + + tmp = malloc(strlen(temp_install) + 32 + strlen(datadir)); + sprintf(tmp, "%s/install/%s", temp_install, datadir); + datadir = tmp; + + /* + * Set up shared library paths to include the temp install. + * + * LD_LIBRARY_PATH covers many platforms. DYLD_LIBRARY_PATH works on + * Darwin, and maybe other Mach-based systems. Windows needs shared + * libraries in PATH. (Only those linked into executables, not + * dlopen'ed ones) Feel free to account for others as well. + */ + add_to_path("LD_LIBRARY_PATH", ':', libdir); + add_to_path("DYLD_LIBRARY_PATH", ':', libdir); +#ifdef WIN32 + add_to_path("PATH", ';', libdir); +#endif + } + else + { + const char *pghost; + const char *pgport; + + /* + * When testing an existing install, we honor existing environment + * variables, except if they're overridden by command line options. + */ + if (hostname != NULL) + { + doputenv("PGHOST", hostname); + unsetenv("PGHOSTADDR"); + } + if (port != -1) + { + char s[16]; + + sprintf(s,"%d",port); + doputenv("PGPORT",s); + } + if (user != NULL) + doputenv("PGUSER", user); + + /* + * On Windows, it seems to be necessary to adjust PATH even in + * this case. + */ +#ifdef WIN32 + add_to_path("PATH", ';', libdir); +#endif + + /* + * Report what we're connecting to + */ + pghost = getenv("PGHOST"); + pgport = getenv("PGPORT"); +#ifndef HAVE_UNIX_SOCKETS + if (!pghost) + pghost = "localhost"; +#endif + + if (pghost && pgport) + printf(_("(using postmaster on %s, port %s)\n"), pghost, pgport); + if (pghost && !pgport) + printf(_("(using postmaster on %s, default port)\n"), pghost); + if (!pghost && pgport) + printf(_("(using postmaster on Unix socket, port %s)\n"), pgport); + if (!pghost && !pgport) + printf(_("(using postmaster on Unix socket, default port)\n")); + } + + load_resultmap(); +} + +/* + * Issue a command via psql, connecting to the specified database + * + * Since we use system(), this doesn't return until the operation finishes + */ +static void +psql_command(const char *database, const char *query, ...) +{ + char query_formatted[1024]; + char query_escaped[2048]; + char psql_cmd[MAXPGPATH + 2048]; + va_list args; + char *s; + char *d; + + /* Generate the query with insertion of sprintf arguments */ + va_start(args, query); + vsnprintf(query_formatted, sizeof(query_formatted), query, args); + va_end(args); + + /* Now escape any shell double-quote metacharacters */ + d = query_escaped; + for (s = query_formatted; *s; s++) + { + if (strchr("\\\"$`", *s)) + *d++ = '\\'; + *d++ = *s; + } + *d = '\0'; + + /* And now we can build and execute the shell command */ + snprintf(psql_cmd, sizeof(psql_cmd), + "\"%s/psql\" -X -c \"%s\" \"%s\"", + bindir, query_escaped, database); + + if (system(psql_cmd) != 0) + { + /* psql probably already reported the error */ + fprintf(stderr, _("command failed: %s\n"), psql_cmd); + exit_nicely(2); + } +} + +/* + * Spawn a process to execute the given shell command; don't wait for it + * + * Returns the process ID so we can wait for it later + */ +static PID_TYPE +spawn_process(const char *cmdline) +{ +#ifndef WIN32 + pid_t pid; + + /* + * Must flush I/O buffers before fork. Ideally we'd use fflush(NULL) here + * ... does anyone still care about systems where that doesn't work? + */ + fflush(stdout); + fflush(stderr); + if (logfile) + fflush(logfile); + + pid = fork(); + if (pid == -1) + { + fprintf(stderr, _("%s: could not fork: %s\n"), + progname, strerror(errno)); + exit_nicely(2); + } + if (pid == 0) + { + /* In child */ + exit(system(cmdline) ? 1 : 0); + } + /* in parent */ + return pid; +#else + char *cmdline2; + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + cmdline2 = malloc(strlen(cmdline) + 8); + sprintf(cmdline2, "cmd /c %s", cmdline); + + if (!CreateProcess(NULL, cmdline2, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) + { + fprintf(stderr, _("failed to start process for \"%s\": %lu\n"), + cmdline2, GetLastError()); + exit_nicely(2); + } + free(cmdline2); + + CloseHandle(pi.hThread); + return pi.hProcess; +#endif +} + +/* + * start a psql test process for specified file (including redirection), + * and return process ID + */ +static PID_TYPE +psql_start_test(const char *testname) +{ + PID_TYPE pid; + char infile[MAXPGPATH]; + char outfile[MAXPGPATH]; + char psql_cmd[MAXPGPATH * 3]; + + snprintf(infile, sizeof(infile), "%s/sql/%s.sql", + inputdir, testname); + snprintf(outfile, sizeof(outfile), "%s/results/%s.out", + outputdir, testname); + + snprintf(psql_cmd, sizeof(psql_cmd), + "\"%s/psql\" -X -a -q -d \"%s\" <\"%s\" >\"%s\" 2>&1", + bindir, dbname, infile, outfile); + + pid = spawn_process(psql_cmd); + + if (pid == INVALID_PID) + { + fprintf(stderr, _("failed to start process for test %s\n"), + testname); + exit_nicely(2); + } + + return pid; +} + +/* + * Count bytes in file + */ +static long +file_size(const char *file) +{ + long r; + FILE *f = fopen(file,"r"); + + if (!f) + { + fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"), + progname, file, strerror(errno)); + return -1; + } + fseek(f, 0, SEEK_END); + r = ftell(f); + fclose(f); + return r; +} + +/* + * Count lines in file + */ +static int +file_line_count(const char *file) +{ + int c; + int l = 0; + FILE *f = fopen(file,"r"); + + if (!f) + { + fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"), + progname, file, strerror(errno)); + return -1; + } + while ((c = fgetc(f)) != EOF) + { + if (c == '\n') + l++; + } + fclose(f); + return l; +} + +static bool +file_exists(const char *file) +{ + FILE *f = fopen(file, "r"); + + if (!f) + return false; + fclose(f); + return true; +} + +static bool +directory_exists(const char *dir) +{ + struct stat st; + + if (stat(dir, &st) != 0) + return false; + if (st.st_mode & S_IFDIR) + return true; + return false; +} + +/* Create a directory */ +static void +make_directory(const char *dir) +{ + if (mkdir(dir, S_IRWXU) < 0) + { + fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"), + progname, dir, strerror(errno)); + exit_nicely(2); + } +} + +/* + * Check the actual result file for the given test against expected results + * + * Returns true if different (failure), false if correct match found. + * In the true case, the diff is appended to the diffs file. + */ +static bool +results_differ(const char *testname) +{ + const char *expectname; + char resultsfile[MAXPGPATH]; + char expectfile[MAXPGPATH]; + char diff[MAXPGPATH]; + char cmd[MAXPGPATH * 3]; + char best_expect_file[MAXPGPATH]; + _resultmap *rm; + FILE *difffile; + int best_line_count; + int i; + int l; + int r; + + /* Check in resultmap if we should be looking at a different file */ + expectname = testname; + for (rm = resultmap; rm != NULL; rm = rm->next) + { + if (strcmp(testname, rm->test) == 0) + { + expectname = rm->resultfile; + break; + } + } + + /* Name of test results file */ + snprintf(resultsfile, sizeof(resultsfile), "%s/results/%s.out", + outputdir, testname); + + /* Name of expected-results file */ + snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out", + inputdir, expectname); + + /* Name to use for temporary diff file */ + snprintf(diff, sizeof(diff), "%s/results/%s.diff", + outputdir, testname); + + /* OK, run the diff */ + snprintf(cmd, sizeof(cmd), + "diff %s \"%s\" \"%s\" >\"%s\"", + basic_diff_opts, expectfile, resultsfile, diff); + r = system(cmd); + if (!WIFEXITED(r) || WEXITSTATUS(r) > 1) + { + fprintf(stderr, _("diff command failed: %s\n"), cmd); + exit_nicely(2); + } + + /* Is the diff file empty? */ + if (file_size(diff) == 0) + { + /* No diff = no changes = good */ + unlink(diff); + return false; + } + + /* There may be secondary comparison files that match better */ + best_line_count = file_line_count(diff); + strcpy(best_expect_file, expectfile); + + for (i = 0; i <= 9; i++) + { + snprintf(expectfile, sizeof(expectfile), "%s/expected/%s_%d.out", + inputdir, expectname, i); + if (!file_exists(expectfile)) + continue; + + snprintf(cmd, sizeof(cmd), + "diff %s \"%s\" \"%s\" >\"%s\"", + basic_diff_opts, expectfile, resultsfile, diff); + r = system(cmd); + if (!WIFEXITED(r) || WEXITSTATUS(r) > 1) + { + fprintf(stderr, _("diff command failed: %s\n"), cmd); + exit_nicely(2); + } + + if (file_size(diff) == 0) + { + /* No diff = no changes = good */ + unlink(diff); + return false; + } + + l = file_line_count(diff); + if (l < best_line_count) + { + /* This diff was a better match than the last one */ + best_line_count = l; + strcpy(best_expect_file, expectfile); + } + } + + /* + * Use the best comparison file to generate the "pretty" diff, which + * we append to the diffs summary file. + */ + snprintf(cmd, sizeof(cmd), + "diff %s \"%s\" \"%s\" >>\"%s\"", + pretty_diff_opts, best_expect_file, resultsfile, difffilename); + r = system(cmd); + if (!WIFEXITED(r) || WEXITSTATUS(r) > 1) + { + fprintf(stderr, _("diff command failed: %s\n"), cmd); + exit_nicely(2); + } + + /* And append a separator */ + difffile = fopen(difffilename, "a"); + if (difffile) + { + fprintf(difffile, + "\n======================================================================\n\n"); + fclose(difffile); + } + + unlink(diff); + return true; +} + +/* + * Wait for specified subprocesses to finish + */ +static void +wait_for_tests(PID_TYPE *pids, int num_tests) +{ +#ifndef WIN32 + int tests_left; + int i; + + tests_left = num_tests; + while (tests_left > 0) + { + pid_t p = wait(NULL); + + if (p == -1) + { + fprintf(stderr, _("failed to wait(): %s\n"), strerror(errno)); + exit_nicely(2); + } + for (i=0; i < num_tests; i++) + { + /* Make sure we only count the processes we explicitly started */ + if (p == pids[i]) + { + pids[i] = -1; + tests_left--; + } + } + } +#else + int r; + int i; + + r = WaitForMultipleObjects(num_tests, pids, TRUE, INFINITE); + if (r != WAIT_OBJECT_0) + { + fprintf(stderr, _("failed to wait for commands to finish: %lu\n"), + GetLastError()); + exit_nicely(2); + } + for (i = 0; i < num_tests; i++) + CloseHandle(pids[i]); +#endif +} + +/* + * Run all the tests specified in one schedule file + */ +static void +run_schedule(const char *schedule) +{ +#define MAX_PARALLEL_TESTS 100 + char *tests[MAX_PARALLEL_TESTS]; + PID_TYPE pids[MAX_PARALLEL_TESTS]; + _stringlist *ignorelist = NULL; + char scbuf[1024]; + FILE *scf; + int line_num = 0; + + scf = fopen(schedule, "r"); + if (!scf) + { + fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"), + progname, schedule, strerror(errno)); + exit_nicely(2); + } + + memset(scbuf, 0, sizeof(scbuf)); + while (fgets(scbuf, sizeof(scbuf)-1, scf)) + { + char *test = NULL; + char *c; + int num_tests; + bool inword; + int i; + + line_num++; + + /* strip trailing whitespace, especially the newline */ + i = strlen(scbuf); + while (i > 0 && isspace((unsigned char) scbuf[i-1])) + scbuf[--i] = '\0'; + + if (scbuf[0] == '\0' || scbuf[0] == '#') + continue; + if (strncmp(scbuf, "test: ", 6) == 0) + test = scbuf + 6; + else if (strncmp(scbuf, "ignore: ", 8) == 0) + { + c = scbuf + 8; + while (*c && isspace((unsigned char) *c)) + c++; + add_stringlist_item(&ignorelist, c); + /* + * Note: ignore: lines do not run the test, they just say that + * failure of this test when run later on is to be ignored. + * A bit odd but that's how the shell-script version did it. + */ + continue; + } + else + { + fprintf(stderr, _("syntax error in schedule file \"%s\" line %d: %s\n"), + schedule, line_num, scbuf); + exit_nicely(2); + } + + num_tests = 0; + inword = false; + for (c = test; *c; c++) + { + if (isspace((unsigned char) *c)) + { + *c = '\0'; + inword = false; + } + else if (!inword) + { + if (num_tests >= MAX_PARALLEL_TESTS) + { + /* can't print scbuf here, it's already been trashed */ + fprintf(stderr, _("too many parallel tests in schedule file \"%s\", line %d\n"), + schedule, line_num); + exit_nicely(2); + } + tests[num_tests] = c; + num_tests++; + inword = true; + } + } + + if (num_tests == 0) + { + fprintf(stderr, _("syntax error in schedule file \"%s\" line %d: %s\n"), + schedule, line_num, scbuf); + exit_nicely(2); + } + + if (num_tests == 1) + { + status(_("test %-20s ... "), tests[0]); + pids[0] = psql_start_test(tests[0]); + wait_for_tests(pids, 1); + /* status line is finished below */ + } + else if (max_connections > 0 && max_connections < num_tests) + { + int oldest = 0; + + status(_("parallel group (%d tests, in groups of %d): "), + num_tests, max_connections); + for (i = 0; i < num_tests; i++) + { + if (i - oldest >= max_connections) + { + wait_for_tests(pids + oldest, i - oldest); + oldest = i; + } + status(" %s", tests[i]); + pids[i] = psql_start_test(tests[i]); + } + wait_for_tests(pids + oldest, i - oldest); + status_end(); + } + else + { + status(_("parallel group (%d tests): "), num_tests); + for (i = 0; i < num_tests; i++) + { + status(" %s", tests[i]); + pids[i] = psql_start_test(tests[i]); + } + wait_for_tests(pids, num_tests); + status_end(); + } + + /* Check results for all tests */ + for (i = 0; i < num_tests; i++) + { + if (num_tests > 1) + status(_(" %-20s ... "), tests[i]); + + if (results_differ(tests[i])) + { + bool ignore = false; + _stringlist *sl; + + for (sl = ignorelist; sl != NULL; sl = sl->next) + { + if (strcmp(tests[i], sl->str) == 0) + { + ignore = true; + break; + } + } + if (ignore) + { + status(_("failed (ignored)")); + fail_ignore_count++; + } + else + { + status(_("FAILED")); + fail_count++; + } + } + else + { + status(_("ok")); + success_count++; + } + + status_end(); + } + } + + fclose(scf); +} + +/* + * Run a single test + */ +static void +run_single_test(const char *test) +{ + PID_TYPE pid; + + status(_("test %-20s ... "), test); + pid = psql_start_test(test); + wait_for_tests(&pid, 1); + + if (results_differ(test)) + { + status(_("FAILED")); + fail_count++; + } + else + { + status(_("ok")); + success_count++; + } + status_end(); +} + +/* + * Create the summary-output files (making them empty if already existing) + */ +static void +open_result_files(void) +{ + char file[MAXPGPATH]; + FILE *difffile; + + /* create the log file (copy of running status output) */ + snprintf(file, sizeof(file), "%s/regression.out", outputdir); + logfilename = strdup(file); + logfile = fopen(logfilename, "w"); + if (!logfile) + { + fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"), + progname, logfilename, strerror(errno)); + exit_nicely(2); + } + + /* create the diffs file as empty */ + snprintf(file, sizeof(file), "%s/regression.diffs", outputdir); + difffilename = strdup(file); + difffile = fopen(difffilename, "w"); + if (!difffile) + { + fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"), + progname, difffilename, strerror(errno)); + exit_nicely(2); + } + /* we don't keep the diffs file open continuously */ + fclose(difffile); + + /* also create the output directory if not present */ + snprintf(file, sizeof(file), "%s/results", outputdir); + if (!directory_exists(file)) + make_directory(file); +} + +static void +help(void) +{ + printf(_("PostgreSQL regression test driver\n")); + printf(_("\n")); + printf(_("Usage: %s [options...] [extra tests...]\n"), progname); + printf(_("\n")); + printf(_("Options:\n")); + printf(_(" --dbname=DB use database DB (default \"regression\")\n")); + printf(_(" --debug turn on debug mode in programs that are run\n")); + printf(_(" --inputdir=DIR take input files from DIR (default \".\")\n")); + printf(_(" --load-language=lang load the named language before running the\n")); + printf(_(" tests; can appear multiple times\n")); + printf(_(" --max-connections=N maximum number of concurrent connections\n")); + printf(_(" (default is 0 meaning unlimited)\n")); + printf(_(" --multibyte=ENCODING use ENCODING as the multibyte encoding\n")); + printf(_(" --outputdir=DIR place output files in DIR (default \".\")\n")); + printf(_(" --schedule=FILE use test ordering schedule from FILE\n")); + printf(_(" (may be used multiple times to concatenate)\n")); + printf(_(" --temp-install=DIR create a temporary installation in DIR\n")); + printf(_(" --no-locale use C locale\n")); + printf(_("\n")); + printf(_("Options for \"temp-install\" mode:\n")); + printf(_(" --top-builddir=DIR (relative) path to top level build directory\n")); + printf(_(" --temp-port=PORT port number to start temp postmaster on\n")); + printf(_("\n")); + printf(_("Options for using an existing installation:\n")); + printf(_(" --host=HOST use postmaster running on HOST\n")); + printf(_(" --port=PORT use postmaster running at PORT\n")); + printf(_(" --user=USER connect as USER\n")); + printf(_("\n")); + printf(_("The exit status is 0 if all tests passed, 1 if some tests failed, and 2\n")); + printf(_("if the tests could not be run for some reason.\n")); + printf(_("\n")); + printf(_("Report bugs to <pgsql-bugs@postgresql.org>.\n")); +} + +int +main(int argc, char *argv[]) +{ + _stringlist *sl; + int c; + int i; + int option_index; + char buf[MAXPGPATH]; + + static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'V'}, + {"dbname", required_argument, NULL, 1}, + {"debug", no_argument, NULL, 2}, + {"inputdir", required_argument, NULL, 3}, + {"load-language", required_argument, NULL, 4}, + {"max-connections", required_argument, NULL, 5}, + {"multibyte", required_argument, NULL, 6}, + {"outputdir", required_argument, NULL, 7}, + {"schedule", required_argument, NULL, 8}, + {"temp-install", required_argument, NULL, 9}, + {"no-locale", no_argument, NULL, 10}, + {"top-builddir", required_argument, NULL, 11}, + {"temp-port", required_argument, NULL, 12}, + {"host", required_argument, NULL, 13}, + {"port", required_argument, NULL, 14}, + {"user", required_argument, NULL, 15}, + {NULL, 0, NULL, 0} + }; + + progname = get_progname(argv[0]); + set_pglocale_pgservice(argv[0], "pg_regress"); + +#ifndef HAVE_UNIX_SOCKETS + /* no unix domain sockets available, so change default */ + hostname = "localhost"; +#endif + + while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1) + { + switch (c) + { + case 'h': + help(); + exit_nicely(0); + case 'V': + printf("pg_regress (PostgreSQL %s)\n", PG_VERSION); + exit_nicely(0); + case 1: + dbname = strdup(optarg); + break; + case 2: + debug = true; + break; + case 3: + inputdir = strdup(optarg); + break; + case 4: + add_stringlist_item(&loadlanguage, optarg); + break; + case 5: + max_connections = atoi(optarg); + break; + case 6: + encoding = strdup(optarg); + break; + case 7: + outputdir = strdup(optarg); + break; + case 8: + add_stringlist_item(&schedulelist, optarg); + break; + case 9: + /* temp_install must be absolute path */ + if (is_absolute_path(optarg)) + temp_install = strdup(optarg); + else + { + char cwdbuf[MAXPGPATH]; + + if (!getcwd(cwdbuf, sizeof(cwdbuf))) + { + fprintf(stderr, _("could not get current working directory: %s\n"), strerror(errno)); + exit_nicely(2); + } + temp_install = malloc(strlen(cwdbuf) + strlen(optarg) + 2); + sprintf(temp_install,"%s/%s", cwdbuf, optarg); + } + canonicalize_path(temp_install); + break; + case 10: + nolocale = true; + break; + case 11: + top_builddir = strdup(optarg); + break; + case 12: + { + int p = atoi(optarg); + + /* Since Makefile isn't very bright, check port range */ + if (p >= 1024 && p <= 65535) + temp_port = p; + } + break; + case 13: + hostname = strdup(optarg); + break; + case 14: + port = atoi(optarg); + break; + case 15: + user = strdup(optarg); + break; + default: + /* getopt_long already emitted a complaint */ + fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"), + progname); + exit_nicely(2); + } + } + + /* + * if we still have arguments, they are extra tests to run + */ + while (argc - optind >= 1) + { + add_stringlist_item(&extra_tests, argv[optind]); + optind++; + } + + if (temp_install) + port = temp_port; + + /* + * Initialization + */ + open_result_files(); + + initialize_environment(); + + if (temp_install) + { + /* + * Prepare the temp installation + */ + if (!top_builddir) + { + fprintf(stderr, _("--top-builddir must be specified when using --temp-install\n")); + exit_nicely(2); + } + + if (directory_exists(temp_install)) + { + header(_("removing existing temp installation")); + rmtree(temp_install,true); + } + + header(_("creating temporary installation")); + + /* make the temp install top directory */ + make_directory(temp_install); + + /* and a directory for log files */ + snprintf(buf, sizeof(buf), "%s/log", outputdir); + if (!directory_exists(buf)) + make_directory(buf); + + /* "make install" */ + snprintf(buf, sizeof(buf), + "\"%s\" -C \"%s\" DESTDIR=\"%s/install\" install with_perl=no with_python=no >\"%s/log/install.log\" 2>&1", + makeprog, top_builddir, temp_install, outputdir); + if (system(buf)) + { + fprintf(stderr, _("\n%s: installation failed\nExamine %s/log/install.log for the reason.\n"), progname, outputdir); + exit_nicely(2); + } + + /* initdb */ + header(_("initializing database system")); + snprintf(buf, sizeof(buf), + "\"%s/initdb\" -D \"%s/data\" -L \"%s\" --noclean %s %s >\"%s/log/initdb.log\" 2>&1", + bindir, temp_install, datadir, + debug ? "--debug" : "", + nolocale ? "--no-locale" : "", + outputdir); + if (system(buf)) + { + fprintf(stderr, _("\n%s: initdb failed\nExamine %s/log/initdb.log for the reason.\n"), progname, outputdir); + exit_nicely(2); + } + + /* + * Start the temp postmaster + */ + header(_("starting postmaster")); + snprintf(buf, sizeof(buf), + "\"%s/postmaster\" -D \"%s/data\" -F %s -c \"listen_addresses=%s\" >\"%s/log/postmaster.log\" 2>&1", + bindir, temp_install, + debug ? "-d 5" : "", + hostname ? hostname : "", + outputdir); + postmaster_pid = spawn_process(buf); + if (postmaster_pid == INVALID_PID) + { + fprintf(stderr, _("\n%s: could not spawn postmaster: %s\n"), + progname, strerror(errno)); + exit_nicely(2); + } + + /* + * XXX Note that because we use system() to launch the subprocess, + * the returned postmaster_pid is not really the PID of the + * postmaster itself; on most systems it'll be the PID of a parent + * shell process. This is OK for the limited purposes we currently + * use postmaster_pid for, but beware! + */ + + /* + * Wait till postmaster is able to accept connections (normally only + * a second or so, but Cygwin is reportedly *much* slower). Don't + * wait forever, however. + */ + snprintf(buf, sizeof(buf), + "\"%s/psql\" -X postgres <%s 2>%s", + bindir, DEVNULL, DEVNULL); + for (i = 0; i < 60; i++) + { + /* Done if psql succeeds */ + if (system(buf) == 0) + break; + + /* + * Fail immediately if postmaster has exited + * + * XXX is there a way to do this on Windows? + */ +#ifndef WIN32 + if (kill(postmaster_pid, 0) != 0) + { + fprintf(stderr, _("\n%s: postmaster failed\nExamine %s/log/postmaster.log for the reason\n"), progname, outputdir); + exit_nicely(2); + } +#endif + + pg_usleep(1000000L); + } + if (i == 60) + { + fprintf(stderr, _("\n%s: postmaster did not start within 60 seconds\nExamine %s/log/postmaster.log for the reason\n"), progname, outputdir); + exit_nicely(2); + } + + postmaster_running = true; + + printf(_("running on port %d with pid %lu\n"), + temp_port, (unsigned long) postmaster_pid); + } + else + { + /* + * Using an existing installation, so may need to get rid of + * pre-existing database. + */ + header(_("dropping database \"%s\""), dbname); + psql_command("postgres","DROP DATABASE IF EXISTS \"%s\"", dbname); + } + + /* + * Create the test database + * + * We use template0 so that any installation-local cruft in template1 + * will not mess up the tests. + */ + header(_("creating database \"%s\""), dbname); + if (encoding) + psql_command("postgres", + "CREATE DATABASE \"%s\" TEMPLATE=template0 ENCODING='%s'", + dbname, encoding); + else /* use installation default */ + psql_command("postgres", + "CREATE DATABASE \"%s\" TEMPLATE=template0", + dbname); + + psql_command(dbname, + "ALTER DATABASE \"%s\" SET lc_messages TO 'C';" + "ALTER DATABASE \"%s\" SET lc_monetary TO 'C';" + "ALTER DATABASE \"%s\" SET lc_numeric TO 'C';" + "ALTER DATABASE \"%s\" SET lc_time TO 'C';", + dbname, dbname, dbname, dbname); + + /* + * Install any requested PL languages + */ + for (sl = loadlanguage; sl != NULL; sl = sl->next) + { + header(_("installing %s"), sl->str); + psql_command(dbname, "CREATE LANGUAGE \"%s\"", sl->str); + } + + /* + * Ready to run the tests + */ + header(_("running regression test queries")); + + for (sl = schedulelist; sl != NULL; sl = sl->next) + { + run_schedule(sl->str); + } + + for (sl = extra_tests; sl != NULL; sl = sl->next) + { + run_single_test(sl->str); + } + + /* + * Shut down temp installation's postmaster + */ + if (temp_install) + { + header(_("shutting down postmaster")); + stop_postmaster(); + } + + fclose(logfile); + + /* + * Emit nice-looking summary message + */ + if (fail_count == 0 && fail_ignore_count == 0) + snprintf(buf, sizeof(buf), + _(" All %d tests passed. "), + success_count); + else if (fail_count == 0) /* fail_count=0, fail_ignore_count>0 */ + snprintf(buf, sizeof(buf), + _(" %d of %d tests passed, %d failed test(s) ignored. "), + success_count, + success_count + fail_ignore_count, + fail_ignore_count); + else if (fail_ignore_count == 0) /* fail_count>0 && fail_ignore_count=0 */ + snprintf(buf, sizeof(buf), + _(" %d of %d tests failed. "), + fail_count, + success_count+fail_count); + else /* fail_count>0 && fail_ignore_count>0 */ + snprintf(buf, sizeof(buf), + _(" %d of %d tests failed, %d of these failures ignored. "), + fail_count+fail_ignore_count, + success_count + fail_count+fail_ignore_count, + fail_ignore_count); + + putchar('\n'); + for (i = strlen(buf); i > 0; i--) + putchar('='); + printf("\n%s\n", buf); + for (i = strlen(buf); i > 0; i--) + putchar('='); + putchar('\n'); + putchar('\n'); + + if (file_size(difffilename) > 0) + { + printf(_("The differences that caused some tests to fail can be viewed in the\n" + "file \"%s\". A copy of the test summary that you see\n" + "above is saved in the file \"%s\".\n\n"), + difffilename, logfilename); + } + else + { + unlink(difffilename); + unlink(logfilename); + } + + if (fail_count != 0) + exit_nicely(1); + + return 0; +} diff --git a/src/test/regress/pg_regress.sh b/src/test/regress/pg_regress.sh deleted file mode 100644 index f56b737d0bcb5dd74f787e6ad413a0d82c745007..0000000000000000000000000000000000000000 --- a/src/test/regress/pg_regress.sh +++ /dev/null @@ -1,777 +0,0 @@ -#! /bin/sh -# $PostgreSQL: pgsql/src/test/regress/pg_regress.sh,v 1.65 2006/07/18 00:32:41 tgl Exp $ - -me=`basename $0` -: ${TMPDIR=/tmp} -TMPFILE=$TMPDIR/pg_regress.$$ - -help="\ -PostgreSQL regression test driver - -Usage: $me [options...] [extra tests...] - -Options: - --dbname=DB use database DB (default \`regression') - --debug turn on debug mode in programs that are run - --inputdir=DIR take input files from DIR (default \`.') - --load-language=lang load the named language before running the - tests; can appear multiple times - --max-connections=N maximum number of concurrent connections - (default is 0 meaning unlimited) - --multibyte=ENCODING use ENCODING as the multibyte encoding, and - also run a test by the same name - --outputdir=DIR place output files in DIR (default \`.') - --schedule=FILE use test ordering schedule from FILE - (may be used multiple times to concatenate) - --temp-install[=DIR] create a temporary installation (in DIR) - --no-locale use C locale - -Options for \`temp-install' mode: - --top-builddir=DIR (relative) path to top level build directory - --temp-port=PORT port number to start temp postmaster on - -Options for using an existing installation: - --host=HOST use postmaster running on HOST - --port=PORT use postmaster running at PORT - --user=USER connect as USER - -The exit status is 0 if all tests passed, 1 if some tests failed, and 2 -if the tests could not be run for some reason. - -Report bugs to <pgsql-bugs@postgresql.org>." - - -message(){ - _dashes='==============' # 14 - _spaces=' ' # 38 - _msg=`echo "$1$_spaces" | cut -c 1-38` - echo "$_dashes $_msg $_dashes" -} - - -# ---------- -# Unset locale settings -# ---------- - -unset LC_COLLATE LC_CTYPE LC_MONETARY LC_MESSAGES LC_NUMERIC LC_TIME LC_ALL LANG LANGUAGE - -# On Windows the default locale may not be English, so force it -case $host_platform in - *-*-cygwin*|*-*-mingw32*) - LANG=en - export LANG - ;; -esac - - -# ---------- -# Check for echo -n vs echo \c -# ---------- - -if echo '\c' | grep c >/dev/null 2>&1; then - ECHO_N='echo -n' - ECHO_C='' -else - ECHO_N='echo' - ECHO_C='\c' -fi - - -# ---------- -# Initialize default settings -# ---------- - -: ${inputdir=.} -: ${outputdir=.} - -libdir='@libdir@' -bindir='@bindir@' -datadir='@datadir@' -host_platform='@host_tuple@' -enable_shared='@enable_shared@' -GCC=@GCC@ - -if [ "$GCC" = yes ]; then - compiler=gcc -else - compiler=cc -fi - -unset mode -unset schedule -unset debug -unset nolocale -unset top_builddir -unset temp_install -unset multibyte - -dbname=regression -hostname=localhost -maxconnections=0 -temp_port=65432 -load_langs="" - -: ${GMAKE='@GMAKE@'} - - -# ---------- -# Parse command line options -# ---------- - -while [ "$#" -gt 0 ] -do - case $1 in - --help|-\?) - echo "$help" - exit 0;; - --version) - echo "pg_regress (PostgreSQL @VERSION@)" - exit 0;; - --dbname=*) - dbname=`expr "x$1" : "x--dbname=\(.*\)"` - shift;; - --debug) - debug=yes - shift;; - --inputdir=*) - inputdir=`expr "x$1" : "x--inputdir=\(.*\)"` - shift;; - --load-language=*) - lang=`expr "x$1" : "x--load-language=\(.*\)"` - load_langs="$load_langs $lang" - unset lang - shift;; - --multibyte=*) - multibyte=`expr "x$1" : "x--multibyte=\(.*\)"` - shift;; - --no-locale) - nolocale=yes - shift;; - --temp-install) - temp_install=./tmp_check - shift;; - --temp-install=*) - temp_install=`expr "x$1" : "x--temp-install=\(.*\)"` - shift;; - --max-connections=*) - maxconnections=`expr "x$1" : "x--max-connections=\(.*\)"` - shift;; - --outputdir=*) - outputdir=`expr "x$1" : "x--outputdir=\(.*\)"` - shift;; - --schedule=*) - foo=`expr "x$1" : "x--schedule=\(.*\)"` - schedule="$schedule $foo" - shift;; - --top-builddir=*) - top_builddir=`expr "x$1" : "x--top-builddir=\(.*\)"` - shift;; - --temp-port=*) - temp_port=`expr "x$1" : "x--temp-port=\(.*\)"` - shift;; - --host=*) - PGHOST=`expr "x$1" : "x--host=\(.*\)"` - export PGHOST - unset PGHOSTADDR - shift;; - --port=*) - PGPORT=`expr "x$1" : "x--port=\(.*\)"` - export PGPORT - shift;; - --user=*) - PGUSER=`expr "x$1" : "x--user=\(.*\)"` - export PGUSER - shift;; - -*) - echo "$me: invalid argument $1" 1>&2 - exit 2;; - *) - extra_tests="$extra_tests $1" - shift;; - esac -done - -# ---------- -# warn of Cygwin likely failure if maxconnections = 0 -# and we are running parallel tests -# ---------- - -case $host_platform in - *-*-cygwin*) - case "$schedule" in - *parallel_schedule*) - if [ $maxconnections -eq 0 ] ; then - echo Using unlimited parallel connections is likely to fail or hang on Cygwin. - echo Try \"$me --max-connections=n\" or \"gmake MAX_CONNECTIONS=n check\" - echo with n = 5 or 10 if this happens. - echo - fi - ;; - esac - ;; -esac - - -# ---------- -# On some platforms we can't use Unix sockets. -# ---------- -case $host_platform in - *-*-cygwin* | *-*-mingw32*) - unix_sockets=no;; - *) - unix_sockets=yes;; -esac - - -# ---------- -# Set up diff to ignore horizontal white space differences. -# ---------- - -case $host_platform in - *-*-sco3.2v5*) - DIFFFLAGS=-b;; - *) - DIFFFLAGS=-w;; -esac - - -# ---------- -# Set backend timezone and datestyle explicitly -# -# To pass the horology test in its current form, the postmaster must be -# started with PGDATESTYLE=ISO, while the frontend must be started with -# PGDATESTYLE=Postgres. We set the postmaster values here and change -# to the frontend settings after the postmaster has been started. -# ---------- - -PGTZ='PST8PDT'; export PGTZ -PGDATESTYLE='ISO, MDY'; export PGDATESTYLE - - -# ---------- -# Exit trap to remove temp file and shut down postmaster -# ---------- - -# Note: There are some stupid shells (even among recent ones) that -# ignore the argument to exit (as in `exit 1') if there is an exit -# trap. The trap (and thus the shell script) will then always exit -# with the result of the last shell command before the `exit'. Hence -# we have to write `(exit x); exit' below this point. - -exit_trap(){ - savestatus=$1 - if [ -n "$postmaster_pid" ]; then - kill -2 "$postmaster_pid" - wait "$postmaster_pid" - unset postmaster_pid - fi - rm -f "$TMPFILE" && exit $savestatus -} - -trap 'exit_trap $?' 0 - -sig_trap() { - savestatus=$1 - echo; echo "caught signal" - if [ -n "$postmaster_pid" ]; then - echo "signalling fast shutdown to postmaster with pid $postmaster_pid" - kill -2 "$postmaster_pid" - wait "$postmaster_pid" - unset postmaster_pid - fi - (exit $savestatus); exit -} - -trap 'sig_trap $?' 1 2 13 15 - - - -# ---------- -# Scan resultmap file to find which platform-specific expected files to use. -# The format of each line of the file is -# testname/hostplatformpattern=substitutefile -# where the hostplatformpattern is evaluated per the rules of expr(1), -# namely, it is a standard regular expression with an implicit ^ at the start. -# What hostplatformpattern will be matched against is the config.guess output -# followed by either ':gcc' or ':cc' (independent of the actual name of the -# compiler executable). -# -# The tempfile hackery is needed because some shells will run the loop -# inside a subshell, whereupon shell variables set therein aren't seen -# outside the loop :-( -# ---------- - -cat /dev/null >$TMPFILE -if [ -f "$inputdir/resultmap" ] -then - while read LINE - do - HOSTPAT=`expr "$LINE" : '.*/\(.*\)='` - if [ `expr "$host_platform:$compiler" : "$HOSTPAT"` -ne 0 ] - then - # remove hostnamepattern from line so that there are no shell - # wildcards in SUBSTLIST; else later 'for' could expand them! - TESTNAME=`expr "$LINE" : '\(.*\)/'` - SUBST=`echo "$LINE" | sed 's/^.*=//'` - echo "$TESTNAME=$SUBST" >> $TMPFILE - fi - done <"$inputdir/resultmap" -fi -SUBSTLIST=`cat $TMPFILE` -rm -f $TMPFILE - - -LOGDIR=$outputdir/log - -if [ x"$temp_install" != x"" ] -then - if echo x"$temp_install" | grep -v '^x/' >/dev/null 2>&1; then - temp_install="`pwd`/$temp_install" - fi - - bindir=$temp_install/install/$bindir - libdir=$temp_install/install/$libdir - datadir=$temp_install/install/$datadir - PGDATA=$temp_install/data - - if [ "$unix_sockets" = no ]; then - PGHOST=$hostname - export PGHOST - unset PGHOSTADDR - else - unset PGHOST - unset PGHOSTADDR - fi - - # since Makefile isn't very bright, check for out-of-range temp_port - if [ "$temp_port" -ge 1024 -a "$temp_port" -le 65535 ] ; then - PGPORT=$temp_port - else - PGPORT=65432 - fi - export PGPORT - - # Get rid of environment stuff that might cause psql to misbehave - # while contacting our temp installation - unset PGDATABASE PGUSER PGSERVICE PGSSLMODE PGREQUIRESSL PGCONNECT_TIMEOUT - - # ---------- - # Set up shared library paths, needed by psql and pg_encoding - # (if you run multibyte). LD_LIBRARY_PATH covers many platforms. - # DYLD_LIBRARY_PATH works on Darwin, and maybe other Mach-based systems. - # Feel free to account for others as well. - # ---------- - - if [ -n "$LD_LIBRARY_PATH" ]; then - LD_LIBRARY_PATH="$libdir:$LD_LIBRARY_PATH" - else - LD_LIBRARY_PATH=$libdir - fi - export LD_LIBRARY_PATH - - if [ -n "$DYLD_LIBRARY_PATH" ]; then - DYLD_LIBRARY_PATH="$libdir:$DYLD_LIBRARY_PATH" - else - DYLD_LIBRARY_PATH=$libdir - fi - export DYLD_LIBRARY_PATH - - # ---------- - # Windows needs shared libraries in PATH. (Only those linked into - # executables, not dlopen'ed ones) - # ---------- - case $host_platform in - *-*-cygwin*|*-*-mingw32*) - PATH=$libdir:$PATH - export PATH - ;; - esac - - if [ -d "$temp_install" ]; then - message "removing existing temp installation" - rm -rf "$temp_install" - fi - - message "creating temporary installation" - if [ ! -d "$LOGDIR" ]; then - mkdir -p "$LOGDIR" || { (exit 2); exit; } - fi - $GMAKE -C "$top_builddir" DESTDIR="$temp_install/install" install with_perl=no with_python=no >"$LOGDIR/install.log" 2>&1 - - if [ $? -ne 0 ] - then - echo - echo "$me: installation failed" - echo "Examine $LOGDIR/install.log for the reason." - echo - (exit 2); exit - fi - - message "initializing database system" - [ "$debug" = yes ] && initdb_options="--debug" - [ "$nolocale" = yes ] && initdb_options="$initdb_options --no-locale" - "$bindir/initdb" -D "$PGDATA" -L "$datadir" --noclean $initdb_options >"$LOGDIR/initdb.log" 2>&1 - - if [ $? -ne 0 ] - then - echo - echo "$me: initdb failed" - echo "Examine $LOGDIR/initdb.log for the reason." - echo - (exit 2); exit - fi - - - # ---------- - # Start postmaster - # ---------- - - message "starting postmaster" - [ "$debug" = yes ] && postmaster_options="$postmaster_options -d 5" - if [ "$unix_sockets" = no ]; then - postmaster_options="$postmaster_options -c listen_addresses=$hostname" - else - postmaster_options="$postmaster_options -c listen_addresses=" - fi - "$bindir/postmaster" -D "$PGDATA" -F $postmaster_options >"$LOGDIR/postmaster.log" 2>&1 & - postmaster_pid=$! - - # Wait till postmaster is able to accept connections (normally only - # a second or so, but Cygwin is reportedly *much* slower). Don't - # wait forever, however. - i=0 - max=60 - until "$bindir/psql" -X $psql_options postgres </dev/null 2>/dev/null - do - i=`expr $i + 1` - if [ $i -ge $max ] - then - break - fi - if kill -0 $postmaster_pid >/dev/null 2>&1 - then - : still starting up - else - break - fi - sleep 1 - done - - if kill -0 $postmaster_pid >/dev/null 2>&1 - then - echo "running on port $PGPORT with pid $postmaster_pid" - else - echo - echo "$me: postmaster did not start" - echo "Examine $LOGDIR/postmaster.log for the reason." - echo - (exit 2); exit - fi - -else # not temp-install - - # ---------- - # Windows needs shared libraries in PATH. (Only those linked into - # executables, not dlopen'ed ones) - # ---------- - case $host_platform in - *-*-cygwin*|*-*-mingw32*) - PATH=$libdir:$PATH - export PATH - ;; - esac - - if [ -n "$PGPORT" ]; then - port_info="port $PGPORT" - else - port_info="default port" - fi - - if [ -n "$PGHOST" ]; then - echo "(using postmaster on $PGHOST, $port_info)" - else - if [ "$unix_sockets" = no ]; then - echo "(using postmaster on localhost, $port_info)" - else - echo "(using postmaster on Unix socket, $port_info)" - fi - fi - - message "dropping database \"$dbname\"" - "$bindir/dropdb" $psql_options "$dbname" - # errors can be ignored -fi - - -# ---------- -# Set up SQL shell for the test. -# ---------- - -psql_test_options="-a -q -X $psql_options" - - -# ---------- -# Set frontend timezone and datestyle explicitly -# ---------- - -PGTZ='PST8PDT'; export PGTZ -PGDATESTYLE='Postgres, MDY'; export PGDATESTYLE - - -# ---------- -# Set up multibyte environment -# ---------- - -if [ -n "$multibyte" ]; then - PGCLIENTENCODING=$multibyte - export PGCLIENTENCODING - encoding_opt="-E $multibyte" -else - unset PGCLIENTENCODING -fi - - -# ---------- -# Create the regression database -# We use template0 so that any installation-local cruft in template1 -# will not mess up the tests. -# ---------- - -message "creating database \"$dbname\"" -"$bindir/createdb" $encoding_opt $psql_options --template template0 "$dbname" -if [ $? -ne 0 ]; then - echo "$me: createdb failed" - (exit 2); exit -fi - -"$bindir/psql" -q -X $psql_options -c "\ -alter database \"$dbname\" set lc_messages to 'C'; -alter database \"$dbname\" set lc_monetary to 'C'; -alter database \"$dbname\" set lc_numeric to 'C'; -alter database \"$dbname\" set lc_time to 'C';" "$dbname" -if [ $? -ne 0 ]; then - echo "$me: could not set database default locales" - (exit 2); exit -fi - - -# ---------- -# Install any requested PL languages -# ---------- - -if [ "$enable_shared" = yes ]; then - for lang in xyzzy $load_langs ; do - if [ "$lang" != "xyzzy" ]; then - message "installing $lang" - "$bindir/createlang" $psql_options $lang $dbname - if [ $? -ne 0 ] && [ $? -ne 2 ]; then - echo "$me: createlang $lang failed" - (exit 2); exit - fi - fi - done -fi - - -# ---------- -# Let's go -# ---------- - -message "running regression test queries" - -if [ ! -d "$outputdir/results" ]; then - mkdir -p "$outputdir/results" || { (exit 2); exit; } -fi -result_summary_file=$outputdir/regression.out -diff_file=$outputdir/regression.diffs - -cat /dev/null >"$result_summary_file" -cat /dev/null >"$diff_file" - -lno=0 -( - [ "$enable_shared" != yes ] && echo "ignore: plpgsql" - cat $schedule </dev/null - for x in $extra_tests; do - echo "test: $x" - done -) | sed 's/[ ]*#.*//g' | \ -while read line -do - # Count line numbers - lno=`expr $lno + 1` - [ -z "$line" ] && continue - - set X $line; shift - - if [ x"$1" = x"ignore:" ]; then - shift - ignore_list="$ignore_list $@" - continue - elif [ x"$1" != x"test:" ]; then - echo "$me:$schedule:$lno: syntax error" - (exit 2); exit - fi - - shift - - # ---------- - # Start tests - # ---------- - - if [ $# -eq 1 ]; then - # Run a single test - formatted=`echo $1 | awk '{printf "%-20.20s", $1;}'` - $ECHO_N "test $formatted ... $ECHO_C" - ( "$bindir/psql" $psql_test_options -d "$dbname" <"$inputdir/sql/$1.sql" >"$outputdir/results/$1.out" 2>&1 )& - wait - else - # Start a parallel group - $ECHO_N "parallel group ($# tests): $ECHO_C" - if [ $maxconnections -gt 0 ] ; then - connnum=0 - test $# -gt $maxconnections && $ECHO_N "(in groups of $maxconnections) $ECHO_C" - fi - for name do - ( - "$bindir/psql" $psql_test_options -d "$dbname" <"$inputdir/sql/$name.sql" >"$outputdir/results/$name.out" 2>&1 - $ECHO_N " $name$ECHO_C" - ) & - if [ $maxconnections -gt 0 ] ; then - connnum=`expr \( $connnum + 1 \) % $maxconnections` - test $connnum -eq 0 && wait - fi - done - wait - echo - fi - - # ---------- - # Run diff - # (We do not want to run the diffs immediately after each test, - # because they would certainly get corrupted if run in parallel - # subshells.) - # ---------- - - for name do - if [ $# -ne 1 ]; then - formatted=`echo "$name" | awk '{printf "%-20.20s", $1;}'` - $ECHO_N " $formatted ... $ECHO_C" - fi - - # Check list extracted from resultmap to see if we should compare - # to a system-specific expected file. - # There shouldn't be multiple matches, but take the last if there are. - - EXPECTED="$inputdir/expected/${name}" - for LINE in $SUBSTLIST - do - if [ `expr "$LINE" : "$name="` -ne 0 ] - then - SUBST=`echo "$LINE" | sed 's/^.*=//'` - EXPECTED="$inputdir/expected/${SUBST}" - fi - done - - # If there are multiple equally valid result files, loop to get the right one. - # If none match, diff against the closest one. - - bestfile= - bestdiff= - result=2 - for thisfile in $EXPECTED.out ${EXPECTED}_[0-9].out; do - [ ! -r "$thisfile" ] && continue - diff $DIFFFLAGS $thisfile $outputdir/results/${name}.out >/dev/null 2>&1 - result=$? - case $result in - 0) break;; - 1) thisdiff=`diff $DIFFFLAGS $thisfile $outputdir/results/${name}.out | wc -l` - if [ -z "$bestdiff" ] || [ "$thisdiff" -lt "$bestdiff" ]; then - bestdiff=$thisdiff; bestfile=$thisfile - fi - continue;; - 2) break;; - esac - done - - # Now print the result. - - case $result in - 0) - echo "ok";; - 1) - ( diff $DIFFFLAGS -C3 $bestfile $outputdir/results/${name}.out - echo - echo "======================================================================" - echo ) >> "$diff_file" - if echo " $ignore_list " | grep " $name " >/dev/null 2>&1 ; then - echo "failed (ignored)" - else - echo "FAILED" - fi - ;; - 2) - # disaster struck - echo "trouble" 1>&2 - (exit 2); exit;; - esac - done -done | tee "$result_summary_file" 2>&1 - -[ $? -ne 0 ] && exit - -# ---------- -# Server shutdown -# ---------- - -if [ -n "$postmaster_pid" ]; then - message "shutting down postmaster" - "$bindir/pg_ctl" -s -D "$PGDATA" stop - wait "$postmaster_pid" - unset postmaster_pid -fi - -rm -f "$TMPFILE" - - -# ---------- -# Evaluation -# ---------- - -count_total=`cat "$result_summary_file" | grep '\.\.\.' | wc -l | sed 's/ //g'` -count_ok=`cat "$result_summary_file" | grep '\.\.\. ok' | wc -l | sed 's/ //g'` -count_failed=`cat "$result_summary_file" | grep '\.\.\. FAILED' | wc -l | sed 's/ //g'` -count_ignored=`cat "$result_summary_file" | grep '\.\.\. failed (ignored)' | wc -l | sed 's/ //g'` - -echo -if [ $count_total -eq $count_ok ]; then - msg="All $count_total tests passed." - result=0 -elif [ $count_failed -eq 0 ]; then - msg="$count_ok of $count_total tests passed, $count_ignored failed test(s) ignored." - result=0 -elif [ $count_ignored -eq 0 ]; then - msg="$count_failed of $count_total tests failed." - result=1 -else - msg="`expr $count_failed + $count_ignored` of $count_total tests failed, $count_ignored of these failures ignored." - result=1 -fi - -dashes=`echo " $msg " | sed 's/./=/g'` -echo "$dashes" -echo " $msg " -echo "$dashes" -echo - -if [ -s "$diff_file" ]; then - echo "The differences that caused some tests to fail can be viewed in the" - echo "file \`$diff_file'. A copy of the test summary that you see" - echo "above is saved in the file \`$result_summary_file'." - echo -else - rm -f "$diff_file" "$result_summary_file" -fi - - -(exit $result); exit