]> granicus.if.org Git - postgresql/blobdiff - src/test/regress/pg_regress.c
Update copyright for the year 2010.
[postgresql] / src / test / regress / pg_regress.c
index 091fe4f906f6bf9ce993a5102721865ee3af71da..551b626d3c6b0ab33ad5570768cc37db14afb08b 100644 (file)
@@ -8,15 +8,15 @@
  *
  * This code is released under the terms of the PostgreSQL License.
  *
- * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/test/regress/pg_regress.c,v 1.13 2006/07/24 01:50:22 adunstan Exp $
+ * $PostgreSQL: pgsql/src/test/regress/pg_regress.c,v 1.69 2010/01/02 16:58:15 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
 
-#include "postgres_fe.h"
+#include "pg_regress.h"
 
 #include <ctype.h>
 #include <sys/stat.h>
 #include <signal.h>
 #include <unistd.h>
 
-#include "getopt_long.h"
-#include "pg_config_paths.h"
-
-#ifndef WIN32
-#define PID_TYPE pid_t
-#define INVALID_PID (-1)
-#else
-#define PID_TYPE HANDLE
-#define INVALID_PID INVALID_HANDLE_VALUE
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/time.h>
+#include <sys/resource.h>
 #endif
 
-
-/* simple list of strings */
-typedef struct _stringlist
-{
-       char *str;
-       struct _stringlist *next;
-} _stringlist;
+#include "getopt_long.h"
+#include "pg_config_paths.h"
 
 /* for resultmap we need a list of pairs of strings */
 typedef struct _resultmap
 {
-       char *test;
-       char *resultfile;
+       char       *test;
+       char       *type;
+       char       *resultfile;
        struct _resultmap *next;
-} _resultmap;
+}      _resultmap;
 
 /*
  * Values obtained from pg_config_paths.h and Makefile.  The PG installation
@@ -57,38 +47,59 @@ typedef struct _resultmap
  * out where "make install" will put stuff under the temp_install directory.
  * In non-temp_install mode, the only thing we need is the location of psql,
  * which we expect to find in psqldir, or in the PATH if psqldir isn't given.
+ *
+ * XXX Because pg_regress is not installed in bindir, we can't support
+ * this for relocatable trees as it is.  --psqldir would need to be
+ * specified in those cases.
  */
-static char *bindir = PGBINDIR;
-static char *libdir = LIBDIR;
-static char *datadir = PGSHAREDIR;
-static char *host_platform = HOST_TUPLE;
+char      *bindir = PGBINDIR;
+char      *libdir = LIBDIR;
+char      *datadir = PGSHAREDIR;
+char      *host_platform = HOST_TUPLE;
+
+#ifndef WIN32_ONLY_COMPILER
 static char *makeprog = MAKEPROG;
+#endif
+
 #ifndef WIN32                                  /* not used in WIN32 case */
 static char *shellprog = SHELLPROG;
 #endif
 
-/* 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";
+/*
+ * On Windows we use -w in diff switches to avoid problems with inconsistent
+ * newline representation.  The actual result files will generally have
+ * Windows-style newlines, but the comparison files might or might not.
+ */
+#ifndef WIN32
+const char *basic_diff_opts = "";
+const char *pretty_diff_opts = "-C3";
+#else
+const char *basic_diff_opts = "-w";
+const char *pretty_diff_opts = "-w -C3";
+#endif
 
 /* options settable from command line */
-static char *dbname = "regression";
-static bool debug = false;
-static char *inputdir = ".";
-static char *outputdir = ".";
+_stringlist *dblist = NULL;
+bool           debug = false;
+char      *inputdir = ".";
+char      *outputdir = ".";
+char      *psqldir = PGBINDIR;
 static _stringlist *loadlanguage = NULL;
-static int max_connections = 0;
+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 *temp_config = NULL;
 static char *top_builddir = NULL;
-static int temp_port = 65432;
 static bool nolocale = false;
-static char *psqldir = NULL;
+static bool use_existing = false;
 static char *hostname = NULL;
-static int port = -1;
+static int     port = -1;
+static bool port_specified_by_user = false;
+static char *dlpath = PKGLIBDIR;
 static char *user = NULL;
+static _stringlist *extraroles = NULL;
 
 /* internal variables */
 static const char *progname;
@@ -101,9 +112,12 @@ 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 int     success_count = 0;
+static int     fail_count = 0;
+static int     fail_ignore_count = 0;
+
+static bool directory_exists(const char *dir);
+static void make_directory(const char *dir);
 
 static void
 header(const char *fmt,...)
@@ -116,17 +130,49 @@ status(const char *fmt,...)
    the supplied arguments. */
 __attribute__((format(printf, 1, 2)));
 static void
-psql_command(const char *database, const char *query, ...)
+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)));
 
+#ifdef WIN32
+typedef BOOL (WINAPI * __CreateRestrictedToken) (HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE);
+
+/* Windows API define missing from MingW headers */
+#define DISABLE_MAX_PRIVILEGE  0x1
+#endif
 
 /*
- * Add an item at the end of a stringlist.
+ * allow core files if possible.
  */
+#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
 static void
-add_stringlist_item(_stringlist **listhead, const char *str)
+unlimit_core_size(void)
+{
+       struct rlimit lim;
+
+       getrlimit(RLIMIT_CORE, &lim);
+       if (lim.rlim_max == 0)
+       {
+               fprintf(stderr,
+                               _("%s: could not set core size: disallowed by hard limit\n"),
+                               progname);
+               return;
+       }
+       else if (lim.rlim_max == RLIM_INFINITY || lim.rlim_cur < lim.rlim_max)
+       {
+               lim.rlim_cur = lim.rlim_max;
+               setrlimit(RLIMIT_CORE, &lim);
+       }
+}
+#endif
+
+
+/*
+ * Add an item at the end of a stringlist.
+ */
+void
+add_stringlist_item(_stringlist ** listhead, const char *str)
 {
        _stringlist *newentry = malloc(sizeof(_stringlist));
        _stringlist *oldentry;
@@ -138,11 +184,43 @@ add_stringlist_item(_stringlist **listhead, const char *str)
        else
        {
                for (oldentry = *listhead; oldentry->next; oldentry = oldentry->next)
-                       /*skip*/;
+                        /* skip */ ;
                oldentry->next = newentry;
        }
 }
 
+/*
+ * Free a stringlist.
+ */
+static void
+free_stringlist(_stringlist ** listhead)
+{
+       if (listhead == NULL || *listhead == NULL)
+               return;
+       if ((*listhead)->next != NULL)
+               free_stringlist(&((*listhead)->next));
+       free((*listhead)->str);
+       free(*listhead);
+       *listhead = NULL;
+}
+
+/*
+ * Split a delimited string into a stringlist
+ */
+static void
+split_to_stringlist(const char *s, const char *delim, _stringlist ** listhead)
+{
+       char       *sc = strdup(s);
+       char       *token = strtok(sc, delim);
+
+       while (token)
+       {
+               add_stringlist_item(listhead, token);
+               token = strtok(NULL, delim);
+       }
+       free(sc);
+}
+
 /*
  * Print a progress banner on stdout.
  */
@@ -202,7 +280,8 @@ stop_postmaster(void)
        if (postmaster_running)
        {
                /* We use pg_ctl to issue the kill and wait for stop */
-               char buf[MAXPGPATH * 2];
+               char            buf[MAXPGPATH * 2];
+               int                     r;
 
                /* On Windows, system() seems not to force fflush, so... */
                fflush(stdout);
@@ -211,7 +290,14 @@ stop_postmaster(void)
                snprintf(buf, sizeof(buf),
                                 SYSTEMQUOTE "\"%s/pg_ctl\" stop -D \"%s/data\" -s -m fast" SYSTEMQUOTE,
                                 bindir, temp_install);
-               system(buf);                    /* ignore exit status */
+               r = system(buf);
+               if (r != 0)
+               {
+                       fprintf(stderr, _("\n%s: could not stop postmaster: exit code was %d\n"),
+                                       progname, r);
+                       exit(2);                        /* not exit_nicely(), that would be recursive */
+               }
+
                postmaster_running = false;
        }
 }
@@ -220,7 +306,7 @@ stop_postmaster(void)
  * Always exit through here, not through plain exit(), to ensure we make
  * an effort to shut down a temp postmaster
  */
-static void
+void
 exit_nicely(int code)
 {
        stop_postmaster();
@@ -300,28 +386,174 @@ string_matches_pattern(const char *str, const char *pattern)
        return false;
 }
 
+/*
+ * Replace all occurances of a string in a string with a different string.
+ * NOTE: Assumes there is enough room in the target buffer!
+ */
+void
+replace_string(char *string, char *replace, char *replacement)
+{
+       char       *ptr;
+
+       while ((ptr = strstr(string, replace)) != NULL)
+       {
+               char       *dup = strdup(string);
+
+               strlcpy(string, dup, ptr - string + 1);
+               strcat(string, replacement);
+               strcat(string, dup + (ptr - string) + strlen(replace));
+               free(dup);
+       }
+}
+
+/*
+ * Convert *.source found in the "source" directory, replacing certain tokens
+ * in the file contents with their intended values, and put the resulting files
+ * in the "dest" directory, replacing the ".source" prefix in their names with
+ * the given suffix.
+ */
+static void
+convert_sourcefiles_in(char *source_subdir, char *dest_subdir, char *suffix)
+{
+       char            testtablespace[MAXPGPATH];
+       char            indir[MAXPGPATH];
+       struct stat st;
+       int                     ret;
+       char      **name;
+       char      **names;
+       int                     count = 0;
+
+       snprintf(indir, MAXPGPATH, "%s/%s", inputdir, source_subdir);
+
+       /* Check that indir actually exists and is a directory */
+       ret = stat(indir, &st);
+       if (ret != 0 || !S_ISDIR(st.st_mode))
+       {
+               /*
+                * No warning, to avoid noise in tests that do not have these
+                * directories; for example, ecpg, contrib and src/pl.
+                */
+               return;
+       }
+
+       names = pgfnames(indir);
+       if (!names)
+               /* Error logged in pgfnames */
+               exit_nicely(2);
+
+       snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir);
+
+#ifdef WIN32
+
+       /*
+        * On Windows only, clean out the test tablespace dir, or create it if it
+        * doesn't exist.  On other platforms we expect the Makefile to take care
+        * of that.  (We don't migrate that functionality in here because it'd be
+        * harder to cope with platform-specific issues such as SELinux.)
+        *
+        * XXX it would be better if pg_regress.c had nothing at all to do with
+        * testtablespace, and this were handled by a .BAT file or similar on
+        * Windows.  See pgsql-hackers discussion of 2008-01-18.
+        */
+       if (directory_exists(testtablespace))
+               rmtree(testtablespace, true);
+       make_directory(testtablespace);
+#endif
+
+       /* finally loop on each file and do the replacement */
+       for (name = names; *name; name++)
+       {
+               char            srcfile[MAXPGPATH];
+               char            destfile[MAXPGPATH];
+               char            prefix[MAXPGPATH];
+               FILE       *infile,
+                                  *outfile;
+               char            line[1024];
+
+               /* reject filenames not finishing in ".source" */
+               if (strlen(*name) < 8)
+                       continue;
+               if (strcmp(*name + strlen(*name) - 7, ".source") != 0)
+                       continue;
+
+               count++;
+
+               /* build the full actual paths to open */
+               snprintf(prefix, strlen(*name) - 6, "%s", *name);
+               snprintf(srcfile, MAXPGPATH, "%s/%s", indir, *name);
+               snprintf(destfile, MAXPGPATH, "%s/%s.%s", dest_subdir, prefix, suffix);
+
+               infile = fopen(srcfile, "r");
+               if (!infile)
+               {
+                       fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
+                                       progname, srcfile, strerror(errno));
+                       exit_nicely(2);
+               }
+               outfile = fopen(destfile, "w");
+               if (!outfile)
+               {
+                       fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+                                       progname, destfile, strerror(errno));
+                       exit_nicely(2);
+               }
+               while (fgets(line, sizeof(line), infile))
+               {
+                       replace_string(line, "@abs_srcdir@", inputdir);
+                       replace_string(line, "@abs_builddir@", outputdir);
+                       replace_string(line, "@testtablespace@", testtablespace);
+                       replace_string(line, "@libdir@", dlpath);
+                       replace_string(line, "@DLSUFFIX@", DLSUFFIX);
+                       fputs(line, outfile);
+               }
+               fclose(infile);
+               fclose(outfile);
+       }
+
+       /*
+        * If we didn't process any files, complain because it probably means
+        * somebody neglected to pass the needed --inputdir argument.
+        */
+       if (count <= 0)
+       {
+               fprintf(stderr, _("%s: no *.source files found in \"%s\"\n"),
+                               progname, indir);
+               exit_nicely(2);
+       }
+
+       pgfnames_cleanup(names);
+}
+
+/* Create the .sql and .out files from the .source files, if any */
+static void
+convert_sourcefiles(void)
+{
+       convert_sourcefiles_in("input", "sql", "sql");
+       convert_sourcefiles_in("output", "expected", "out");
+}
+
 /*
  * Scan resultmap file to find which platform-specific expected files to use.
  *
  * The format of each line of the file is
- *         testname/hostplatformpattern=substitutefile
+ *                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,
+ * 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;
+       char            buf[MAXPGPATH];
+       FILE       *f;
 
        /* scan the file ... */
        snprintf(buf, sizeof(buf), "%s/resultmap", inputdir);
-       f = fopen(buf,"r");
+       f = fopen(buf, "r");
        if (!f)
        {
                /* OK if it doesn't exist, else complain */
@@ -334,17 +566,27 @@ load_resultmap(void)
 
        while (fgets(buf, sizeof(buf), f))
        {
-               char *platform;
-               char *expected;
-               int i;
+               char       *platform;
+               char       *file_type;
+               char       *expected;
+               int                     i;
 
                /* strip trailing whitespace, especially the newline */
                i = strlen(buf);
-               while (i > 0 && isspace((unsigned char) buf[i-1]))
+               while (i > 0 && isspace((unsigned char) buf[i - 1]))
                        buf[--i] = '\0';
 
                /* parse out the line fields */
-               platform = strchr(buf, '/');
+               file_type = strchr(buf, ':');
+               if (!file_type)
+               {
+                       fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
+                                       buf);
+                       exit_nicely(2);
+               }
+               *file_type++ = '\0';
+
+               platform = strchr(file_type, ':');
                if (!platform)
                {
                        fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
@@ -362,16 +604,17 @@ load_resultmap(void)
                *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 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->type = strdup(file_type);
                        entry->resultfile = strdup(expected);
                        entry->next = resultmap;
                        resultmap = entry;
@@ -380,13 +623,43 @@ load_resultmap(void)
        fclose(f);
 }
 
+/*
+ * Check in resultmap if we should be looking at a different file
+ */
+static
+const char *
+get_expectfile(const char *testname, const char *file)
+{
+       char       *file_type;
+       _resultmap *rm;
+
+       /*
+        * Determine the file type from the file name. This is just what is
+        * following the last dot in the file name.
+        */
+       if (!file || !(file_type = strrchr(file, '.')))
+               return NULL;
+
+       file_type++;
+
+       for (rm = resultmap; rm != NULL; rm = rm->next)
+       {
+               if (strcmp(testname, rm->test) == 0 && strcmp(file_type, rm->type) == 0)
+               {
+                       return rm->resultfile;
+               }
+       }
+
+       return NULL;
+}
+
 /*
  * 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);
+       char       *s = malloc(strlen(var) + strlen(val) + 2);
 
        sprintf(s, "%s=%s", var, val);
        putenv(s);
@@ -399,8 +672,8 @@ doputenv(const char *var, const char *val)
 static void
 add_to_path(const char *pathname, char separator, const char *addval)
 {
-       char *oldval = getenv(pathname);
-       char *newval;
+       char       *oldval = getenv(pathname);
+       char       *newval;
 
        if (!oldval || !oldval[0])
        {
@@ -411,7 +684,7 @@ add_to_path(const char *pathname, char separator, const char *addval)
        else
        {
                newval = malloc(strlen(pathname) + strlen(addval) + strlen(oldval) + 3);
-               sprintf(newval,"%s=%s%c%s",pathname,addval,separator,oldval);
+               sprintf(newval, "%s=%s%c%s", pathname, addval, separator, oldval);
        }
        putenv(newval);
 }
@@ -422,24 +695,34 @@ add_to_path(const char *pathname, char separator, const char *addval)
 static void
 initialize_environment(void)
 {
-       char *tmp;
+       char       *tmp;
+
+       if (nolocale)
+       {
+               /*
+                * Clear out any non-C locale settings
+                */
+               unsetenv("LC_COLLATE");
+               unsetenv("LC_CTYPE");
+               unsetenv("LC_MONETARY");
+               unsetenv("LC_NUMERIC");
+               unsetenv("LC_TIME");
+               unsetenv("LANG");
+               /* On Windows the default locale cannot be English, so force it */
+#if defined(WIN32) || defined(__CYGWIN__)
+               putenv("LANG=en");
+#endif
+       }
 
        /*
-        * Clear out any non-C locale settings
+        * Set translation-related settings to English; otherwise psql will
+        * produce translated messages and produce diffs.  (XXX If we ever support
+        * translation of pg_regress, this needs to be moved elsewhere, where psql
+        * is actually called.)
         */
-       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
+       unsetenv("LC_ALL");
+       putenv("LC_MESSAGES=C");
 
        /*
         * Set multibyte as requested
@@ -455,15 +738,32 @@ initialize_environment(void)
        putenv("PGTZ=PST8PDT");
        putenv("PGDATESTYLE=Postgres, MDY");
 
+       /*
+        * Likewise set intervalstyle to ensure consistent results.  This is a bit
+        * more painful because we must use PGOPTIONS, and we want to preserve the
+        * user's ability to set other variables through that.
+        */
+       {
+               const char *my_pgoptions = "-c intervalstyle=postgres_verbose";
+               const char *old_pgoptions = getenv("PGOPTIONS");
+               char       *new_pgoptions;
+
+               if (!old_pgoptions)
+                       old_pgoptions = "";
+               new_pgoptions = malloc(strlen(old_pgoptions) + strlen(my_pgoptions) + 12);
+               sprintf(new_pgoptions, "PGOPTIONS=%s %s", old_pgoptions, my_pgoptions);
+               putenv(new_pgoptions);
+       }
+
        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.
+                * 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");
@@ -479,10 +779,10 @@ initialize_environment(void)
                unsetenv("PGHOSTADDR");
                if (port != -1)
                {
-                       char s[16];
+                       char            s[16];
 
-                       sprintf(s,"%d",port);
-                       doputenv("PGPORT",s);
+                       sprintf(s, "%d", port);
+                       doputenv("PGPORT", s);
                }
 
                /*
@@ -507,13 +807,15 @@ initialize_environment(void)
                 * 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.
+                * Darwin, and maybe other Mach-based systems.  LIBPATH is for AIX.
+                * 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);
-#if defined(WIN32) || defined(CYGWIN)
+               add_to_path("LIBPATH", ':', libdir);
+#if defined(WIN32) || defined(__CYGWIN__)
                add_to_path("PATH", ';', libdir);
 #endif
        }
@@ -533,10 +835,10 @@ initialize_environment(void)
                }
                if (port != -1)
                {
-                       char s[16];
+                       char            s[16];
 
-                       sprintf(s,"%d",port);
-                       doputenv("PGPORT",s);
+                       sprintf(s, "%d", port);
+                       doputenv("PGPORT", s);
                }
                if (user != NULL)
                        doputenv("PGUSER", user);
@@ -561,6 +863,7 @@ initialize_environment(void)
                        printf(_("(using postmaster on Unix socket, default port)\n"));
        }
 
+       convert_sourcefiles();
        load_resultmap();
 }
 
@@ -570,14 +873,14 @@ initialize_environment(void)
  * Since we use system(), this doesn't return until the operation finishes
  */
 static void
-psql_command(const char *database, const char *query, ...)
+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;
+       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);
@@ -613,16 +916,16 @@ psql_command(const char *database, const char *query, ...)
 /*
  * 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
+ * Returns the process ID (or HANDLE) so we can wait for it later
  */
-static PID_TYPE
+PID_TYPE
 spawn_process(const char *cmdline)
 {
 #ifndef WIN32
-       pid_t pid;
+       pid_t           pid;
 
        /*
-        * Must flush I/O buffers before fork.  Ideally we'd use fflush(NULL) here
+        * 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);
@@ -642,14 +945,14 @@ spawn_process(const char *cmdline)
                /*
                 * In child
                 *
-                * Instead of using system(), exec the shell directly, and tell it
-                * to "exec" the command too.  This saves two useless processes
-                * per parallel test case.
+                * Instead of using system(), exec the shell directly, and tell it to
+                * "exec" the command too.      This saves two useless processes per
+                * parallel test case.
                 */
-               char *cmdline2 = malloc(strlen(cmdline) + 6);
+               char       *cmdline2 = malloc(strlen(cmdline) + 6);
 
                sprintf(cmdline2, "exec %s", cmdline);
-               execl(shellprog, shellprog, "-c", cmdline2, NULL);
+               execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
                fprintf(stderr, _("%s: could not exec \"%s\": %s\n"),
                                progname, shellprog, strerror(errno));
                exit(1);                                /* not exit_nicely here... */
@@ -657,64 +960,104 @@ spawn_process(const char *cmdline)
        /* in parent */
        return pid;
 #else
-       char *cmdline2;
+       char       *cmdline2;
+       BOOL            b;
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
+       HANDLE          origToken;
+       HANDLE          restrictedToken;
+       SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
+       SID_AND_ATTRIBUTES dropSids[2];
+       __CreateRestrictedToken _CreateRestrictedToken = NULL;
+       HANDLE          Advapi32Handle;
 
        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
 
-       cmdline2 = malloc(strlen(cmdline) + 8);
-       sprintf(cmdline2, "cmd /c %s", cmdline);
+       Advapi32Handle = LoadLibrary("ADVAPI32.DLL");
+       if (Advapi32Handle != NULL)
+       {
+               _CreateRestrictedToken = (__CreateRestrictedToken) GetProcAddress(Advapi32Handle, "CreateRestrictedToken");
+       }
 
-       if (!CreateProcess(NULL, cmdline2, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi))
+       if (_CreateRestrictedToken == NULL)
        {
-               fprintf(stderr, _("could not start process for \"%s\": %lu\n"),
-                               cmdline2, GetLastError());
+               if (Advapi32Handle != NULL)
+                       FreeLibrary(Advapi32Handle);
+               fprintf(stderr, _("%s: cannot create restricted tokens on this platform\n"),
+                               progname);
                exit_nicely(2);
        }
-       free(cmdline2);
 
-       CloseHandle(pi.hThread);
-       return pi.hProcess;
-#endif
-}
+       /* Open the current token to use as base for the restricted one */
+       if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken))
+       {
+               fprintf(stderr, _("could not open process token: %lu\n"),
+                               GetLastError());
+               exit_nicely(2);
+       }
 
-/*
- * 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];
+       /* Allocate list of SIDs to remove */
+       ZeroMemory(&dropSids, sizeof(dropSids));
+       if (!AllocateAndInitializeSid(&NtAuthority, 2,
+                                                                 SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &dropSids[0].Sid) ||
+               !AllocateAndInitializeSid(&NtAuthority, 2,
+                                                                 SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0, 0, &dropSids[1].Sid))
+       {
+               fprintf(stderr, _("could not allocate SIDs: %lu\n"), GetLastError());
+               exit_nicely(2);
+       }
 
-       snprintf(infile, sizeof(infile), "%s/sql/%s.sql",
-                        inputdir, testname);
-       snprintf(outfile, sizeof(outfile), "%s/results/%s.out",
-                        outputdir, testname);
+       b = _CreateRestrictedToken(origToken,
+                                                          DISABLE_MAX_PRIVILEGE,
+                                                          sizeof(dropSids) / sizeof(dropSids[0]),
+                                                          dropSids,
+                                                          0, NULL,
+                                                          0, NULL,
+                                                          &restrictedToken);
 
-       snprintf(psql_cmd, sizeof(psql_cmd),
-                        SYSTEMQUOTE "\"%s%spsql\" -X -a -q -d \"%s\" < \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE,
-                        psqldir ? psqldir : "",
-                        psqldir ? "/" : "",
-                        dbname,
-                        infile,
-                        outfile);
+       FreeSid(dropSids[1].Sid);
+       FreeSid(dropSids[0].Sid);
+       CloseHandle(origToken);
+       FreeLibrary(Advapi32Handle);
 
-       pid = spawn_process(psql_cmd);
+       if (!b)
+       {
+               fprintf(stderr, _("could not create restricted token: %lu\n"),
+                               GetLastError());
+               exit_nicely(2);
+       }
 
-       if (pid == INVALID_PID)
+       cmdline2 = malloc(strlen(cmdline) + 8);
+       sprintf(cmdline2, "cmd /c %s", cmdline);
+
+#ifndef __CYGWIN__
+       AddUserToTokenDacl(restrictedToken);
+#endif
+
+       if (!CreateProcessAsUser(restrictedToken,
+                                                        NULL,
+                                                        cmdline2,
+                                                        NULL,
+                                                        NULL,
+                                                        TRUE,
+                                                        CREATE_SUSPENDED,
+                                                        NULL,
+                                                        NULL,
+                                                        &si,
+                                                        &pi))
        {
-               fprintf(stderr, _("could not start process for test %s\n"),
-                               testname);
+               fprintf(stderr, _("could not start process for \"%s\": %lu\n"),
+                               cmdline2, GetLastError());
                exit_nicely(2);
        }
 
-       return pid;
+       free(cmdline2);
+
+       ResumeThread(pi.hThread);
+       CloseHandle(pi.hThread);
+       return pi.hProcess;
+#endif
 }
 
 /*
@@ -723,8 +1066,8 @@ psql_start_test(const char *testname)
 static long
 file_size(const char *file)
 {
-       long r;
-       FILE *f = fopen(file,"r");
+       long            r;
+       FILE       *f = fopen(file, "r");
 
        if (!f)
        {
@@ -744,9 +1087,9 @@ file_size(const char *file)
 static int
 file_line_count(const char *file)
 {
-       int c;
-       int l = 0;
-       FILE *f = fopen(file,"r");
+       int                     c;
+       int                     l = 0;
+       FILE       *f = fopen(file, "r");
 
        if (!f)
        {
@@ -763,10 +1106,10 @@ file_line_count(const char *file)
        return l;
 }
 
-static bool
+bool
 file_exists(const char *file)
 {
-       FILE *f = fopen(file, "r");
+       FILE       *f = fopen(file, "r");
 
        if (!f)
                return false;
@@ -781,7 +1124,7 @@ directory_exists(const char *dir)
 
        if (stat(dir, &st) != 0)
                return false;
-       if (st.st_mode & S_IFDIR)
+       if (S_ISDIR(st.st_mode))
                return true;
        return false;
 }
@@ -790,7 +1133,7 @@ directory_exists(const char *dir)
 static void
 make_directory(const char *dir)
 {
-       if (mkdir(dir, S_IRWXU) < 0)
+       if (mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO) < 0)
        {
                fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
                                progname, dir, strerror(errno));
@@ -799,29 +1142,58 @@ make_directory(const char *dir)
 }
 
 /*
- * Run a "diff" command and check that it didn't crash
+ * In: filename.ext, Return: filename_i.ext, where 0 < i <= 9
  */
-static void
-run_diff(const char *cmd)
+static char *
+get_alternative_expectfile(const char *expectfile, int i)
 {
-       int r;
+       char       *last_dot;
+       int                     ssize = strlen(expectfile) + 2 + 1;
+       char       *tmp = (char *) malloc(ssize);
+       char       *s = (char *) malloc(ssize);
+
+       strcpy(tmp, expectfile);
+       last_dot = strrchr(tmp, '.');
+       if (!last_dot)
+       {
+               free(tmp);
+               free(s);
+               return NULL;
+       }
+       *last_dot = '\0';
+       snprintf(s, ssize, "%s_%d.%s", tmp, i, last_dot + 1);
+       free(tmp);
+       return s;
+}
+
+/*
+ * Run a "diff" command and also check that it didn't crash
+ */
+static int
+run_diff(const char *cmd, const char *filename)
+{
+       int                     r;
 
        r = system(cmd);
-       /*
-        * XXX FIXME: it appears that include/port/win32.h's definitions of
-        * WIFEXITED and related macros may be wrong.  They certainly don't
-        * work for inspecting the results of system().  For the moment,
-        * hard-wire the check on Windows.
-        */
-#ifndef WIN32
        if (!WIFEXITED(r) || WEXITSTATUS(r) > 1)
-#else
-       if (r != 0 && r != 1)
-#endif
        {
                fprintf(stderr, _("diff command failed with status %d: %s\n"), r, cmd);
                exit_nicely(2);
        }
+#ifdef WIN32
+
+       /*
+        * On WIN32, if the 'diff' command cannot be found, system() returns 1,
+        * but produces nothing to stdout, so we check for that here.
+        */
+       if (WEXITSTATUS(r) == 1 && file_size(filename) <= 0)
+       {
+               fprintf(stderr, _("diff command not found: %s\n"), cmd);
+               exit_nicely(2);
+       }
+#endif
+
+       return WEXITSTATUS(r);
 }
 
 /*
@@ -831,53 +1203,48 @@ run_diff(const char *cmd)
  * In the true case, the diff is appended to the diffs file.
  */
 static bool
-results_differ(const char *testname)
+results_differ(const char *testname, const char *resultsfile, const char *default_expectfile)
 {
-       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;
+       char            expectfile[MAXPGPATH];
+       char            diff[MAXPGPATH];
+       char            cmd[MAXPGPATH * 3];
+       char            best_expect_file[MAXPGPATH];
+       FILE       *difffile;
+       int                     best_line_count;
+       int                     i;
+       int                     l;
+       const char *platform_expectfile;
 
-       /* 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;
-               }
-       }
+       /*
+        * We can pass either the resultsfile or the expectfile, they should have
+        * the same type (filename.type) anyway.
+        */
+       platform_expectfile = get_expectfile(testname, resultsfile);
 
-       /* Name of test results file */
-       snprintf(resultsfile, sizeof(resultsfile), "%s/results/%s.out",
-                        outputdir, testname);
+       strcpy(expectfile, default_expectfile);
+       if (platform_expectfile)
+       {
+               /*
+                * Replace everything afer the last slash in expectfile with what the
+                * platform_expectfile contains.
+                */
+               char       *p = strrchr(expectfile, '/');
 
-       /* Name of expected-results file */
-       snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out",
-                        inputdir, expectname);
+               if (p)
+                       strcpy(++p, platform_expectfile);
+       }
 
        /* Name to use for temporary diff file */
-       snprintf(diff, sizeof(diff), "%s/results/%s.diff",
-                        outputdir, testname);
+       snprintf(diff, sizeof(diff), "%s.diff", resultsfile);
 
        /* OK, run the diff */
        snprintf(cmd, sizeof(cmd),
                         SYSTEMQUOTE "diff %s \"%s\" \"%s\" > \"%s\"" SYSTEMQUOTE,
                         basic_diff_opts, expectfile, resultsfile, diff);
-       run_diff(cmd);
 
        /* Is the diff file empty? */
-       if (file_size(diff) == 0)
+       if (run_diff(cmd, diff) == 0)
        {
-               /* No diff = no changes = good */
                unlink(diff);
                return false;
        }
@@ -888,17 +1255,44 @@ results_differ(const char *testname)
 
        for (i = 0; i <= 9; i++)
        {
-               snprintf(expectfile, sizeof(expectfile), "%s/expected/%s_%d.out",
-                                inputdir, expectname, i);
-               if (!file_exists(expectfile))
+               char       *alt_expectfile;
+
+               alt_expectfile = get_alternative_expectfile(expectfile, i);
+               if (!file_exists(alt_expectfile))
                        continue;
 
                snprintf(cmd, sizeof(cmd),
                                 SYSTEMQUOTE "diff %s \"%s\" \"%s\" > \"%s\"" SYSTEMQUOTE,
-                                basic_diff_opts, expectfile, resultsfile, diff);
-               run_diff(cmd);
+                                basic_diff_opts, alt_expectfile, resultsfile, diff);
+
+               if (run_diff(cmd, diff) == 0)
+               {
+                       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, alt_expectfile);
+               }
+               free(alt_expectfile);
+       }
+
+       /*
+        * fall back on the canonical results file if we haven't tried it yet and
+        * haven't found a complete match yet.
+        */
+
+       if (platform_expectfile)
+       {
+               snprintf(cmd, sizeof(cmd),
+                                SYSTEMQUOTE "diff %s \"%s\" \"%s\" > \"%s\"" SYSTEMQUOTE,
+                                basic_diff_opts, default_expectfile, resultsfile, diff);
 
-               if (file_size(diff) == 0)
+               if (run_diff(cmd, diff) == 0)
                {
                        /* No diff = no changes = good */
                        unlink(diff);
@@ -910,18 +1304,18 @@ results_differ(const char *testname)
                {
                        /* This diff was a better match than the last one */
                        best_line_count = l;
-                       strcpy(best_expect_file, expectfile);
+                       strcpy(best_expect_file, default_expectfile);
                }
        }
 
        /*
-        * Use the best comparison file to generate the "pretty" diff, which
-        * we append to the diffs summary file.
+        * Use the best comparison file to generate the "pretty" diff, which we
+        * append to the diffs summary file.
         */
        snprintf(cmd, sizeof(cmd),
                         SYSTEMQUOTE "diff %s \"%s\" \"%s\" >> \"%s\"" SYSTEMQUOTE,
                         pretty_diff_opts, best_expect_file, resultsfile, difffilename);
-       run_diff(cmd);
+       run_diff(cmd, difffilename);
 
        /* And append a separator */
        difffile = fopen(difffilename, "a");
@@ -937,20 +1331,21 @@ results_differ(const char *testname)
 }
 
 /*
- * Wait for specified subprocesses to finish
+ * Wait for specified subprocesses to finish, and return their exit
+ * statuses into statuses[]
  *
- * If names isn't NULL, report each subprocess as it finishes
+ * If names isn't NULL, print each subprocess's name as it finishes
  *
  * Note: it's OK to scribble on the pids array, but not on the names array
  */
 static void
-wait_for_tests(PID_TYPE *pids, char **names, int num_tests)
+wait_for_tests(PID_TYPE * pids, int *statuses, char **names, int num_tests)
 {
-       int tests_left;
-       int i;
+       int                     tests_left;
+       int                     i;
 
 #ifdef WIN32
-       PID_TYPE *active_pids = malloc(num_tests * sizeof(PID_TYPE));
+       PID_TYPE   *active_pids = malloc(num_tests * sizeof(PID_TYPE));
 
        memcpy(active_pids, pids, num_tests * sizeof(PID_TYPE));
 #endif
@@ -958,10 +1353,12 @@ wait_for_tests(PID_TYPE *pids, char **names, int num_tests)
        tests_left = num_tests;
        while (tests_left > 0)
        {
-               PID_TYPE p;
+               PID_TYPE        p;
 
 #ifndef WIN32
-               p = wait(NULL);
+               int                     exit_status;
+
+               p = wait(&exit_status);
 
                if (p == INVALID_PID)
                {
@@ -970,7 +1367,8 @@ wait_for_tests(PID_TYPE *pids, char **names, int num_tests)
                        exit_nicely(2);
                }
 #else
-               int r;
+               DWORD           exit_status;
+               int                     r;
 
                r = WaitForMultipleObjects(tests_left, active_pids, FALSE, INFINITE);
                if (r < WAIT_OBJECT_0 || r >= WAIT_OBJECT_0 + tests_left)
@@ -982,16 +1380,18 @@ wait_for_tests(PID_TYPE *pids, char **names, int num_tests)
                p = active_pids[r - WAIT_OBJECT_0];
                /* compact the active_pids array */
                active_pids[r - WAIT_OBJECT_0] = active_pids[tests_left - 1];
-#endif /* WIN32 */
+#endif   /* WIN32 */
 
-               for (i=0; i < num_tests; i++)
+               for (i = 0; i < num_tests; i++)
                {
                        if (p == pids[i])
                        {
 #ifdef WIN32
+                               GetExitCodeProcess(pids[i], &exit_status);
                                CloseHandle(pids[i]);
 #endif
                                pids[i] = INVALID_PID;
+                               statuses[i] = (int) exit_status;
                                if (names)
                                        status(" %s", names[i]);
                                tests_left--;
@@ -1005,19 +1405,56 @@ wait_for_tests(PID_TYPE *pids, char **names, int num_tests)
 #endif
 }
 
+/*
+ * report nonzero exit code from a test process
+ */
+static void
+log_child_failure(int exitstatus)
+{
+       if (WIFEXITED(exitstatus))
+               status(_(" (test process exited with exit code %d)"),
+                          WEXITSTATUS(exitstatus));
+       else if (WIFSIGNALED(exitstatus))
+       {
+#if defined(WIN32)
+               status(_(" (test process was terminated by exception 0x%X)"),
+                          WTERMSIG(exitstatus));
+#elif defined(HAVE_DECL_SYS_SIGLIST) && HAVE_DECL_SYS_SIGLIST
+               status(_(" (test process was terminated by signal %d: %s)"),
+                          WTERMSIG(exitstatus),
+                          WTERMSIG(exitstatus) < NSIG ?
+                          sys_siglist[WTERMSIG(exitstatus)] : "(unknown))");
+#else
+               status(_(" (test process was terminated by signal %d)"),
+                          WTERMSIG(exitstatus));
+#endif
+       }
+       else
+               status(_(" (test process exited with unrecognized status %d)"),
+                          exitstatus);
+}
+
 /*
  * Run all the tests specified in one schedule file
  */
 static void
-run_schedule(const char *schedule)
+run_schedule(const char *schedule, test_function tfunc)
 {
 #define MAX_PARALLEL_TESTS 100
-       char *tests[MAX_PARALLEL_TESTS];
-       PID_TYPE pids[MAX_PARALLEL_TESTS];
+       char       *tests[MAX_PARALLEL_TESTS];
+       _stringlist *resultfiles[MAX_PARALLEL_TESTS];
+       _stringlist *expectfiles[MAX_PARALLEL_TESTS];
+       _stringlist *tags[MAX_PARALLEL_TESTS];
+       PID_TYPE        pids[MAX_PARALLEL_TESTS];
+       int                     statuses[MAX_PARALLEL_TESTS];
        _stringlist *ignorelist = NULL;
-       char scbuf[1024];
-       FILE *scf;
-       int line_num = 0;
+       char            scbuf[1024];
+       FILE       *scf;
+       int                     line_num = 0;
+
+       memset(resultfiles, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
+       memset(expectfiles, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
+       memset(tags, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
 
        scf = fopen(schedule, "r");
        if (!scf)
@@ -1029,17 +1466,26 @@ run_schedule(const char *schedule)
 
        while (fgets(scbuf, sizeof(scbuf), scf))
        {
-               char *test = NULL;
-               char *c;
-               int num_tests;
-               bool inword;
-               int i;
+               char       *test = NULL;
+               char       *c;
+               int                     num_tests;
+               bool            inword;
+               int                     i;
 
                line_num++;
 
+               for (i = 0; i < MAX_PARALLEL_TESTS; i++)
+               {
+                       if (resultfiles[i] == NULL)
+                               break;
+                       free_stringlist(&resultfiles[i]);
+                       free_stringlist(&expectfiles[i]);
+                       free_stringlist(&tags[i]);
+               }
+
                /* strip trailing whitespace, especially the newline */
                i = strlen(scbuf);
-               while (i > 0 && isspace((unsigned char) scbuf[i-1]))
+               while (i > 0 && isspace((unsigned char) scbuf[i - 1]))
                        scbuf[--i] = '\0';
 
                if (scbuf[0] == '\0' || scbuf[0] == '#')
@@ -1052,10 +1498,11 @@ run_schedule(const char *schedule)
                        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.
+                        * 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;
                }
@@ -1099,14 +1546,14 @@ run_schedule(const char *schedule)
 
                if (num_tests == 1)
                {
-                       status(_("test %-20s ... "), tests[0]);
-                       pids[0] = psql_start_test(tests[0]);
-                       wait_for_tests(pids, NULL, 1);
+                       status(_("test %-24s ... "), tests[0]);
+                       pids[0] = (tfunc) (tests[0], &resultfiles[0], &expectfiles[0], &tags[0]);
+                       wait_for_tests(pids, statuses, NULL, 1);
                        /* status line is finished below */
                }
                else if (max_connections > 0 && max_connections < num_tests)
                {
-                       int oldest = 0;
+                       int                     oldest = 0;
 
                        status(_("parallel group (%d tests, in groups of %d): "),
                                   num_tests, max_connections);
@@ -1114,12 +1561,14 @@ run_schedule(const char *schedule)
                        {
                                if (i - oldest >= max_connections)
                                {
-                                       wait_for_tests(pids + oldest, tests + oldest, i - oldest);
+                                       wait_for_tests(pids + oldest, statuses + oldest,
+                                                                  tests + oldest, i - oldest);
                                        oldest = i;
                                }
-                               pids[i] = psql_start_test(tests[i]);
+                               pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
                        }
-                       wait_for_tests(pids + oldest, tests + oldest, i - oldest);
+                       wait_for_tests(pids + oldest, statuses + oldest,
+                                                  tests + oldest, i - oldest);
                        status_end();
                }
                else
@@ -1127,21 +1576,51 @@ run_schedule(const char *schedule)
                        status(_("parallel group (%d tests): "), num_tests);
                        for (i = 0; i < num_tests; i++)
                        {
-                               pids[i] = psql_start_test(tests[i]);
+                               pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
                        }
-                       wait_for_tests(pids, tests, num_tests);
+                       wait_for_tests(pids, statuses, tests, num_tests);
                        status_end();
                }
 
                /* Check results for all tests */
                for (i = 0; i < num_tests; i++)
                {
+                       _stringlist *rl,
+                                          *el,
+                                          *tl;
+                       bool            differ = false;
+
                        if (num_tests > 1)
-                               status(_("     %-20s ... "), tests[i]);
+                               status(_("     %-24s ... "), tests[i]);
 
-                       if (results_differ(tests[i]))
+                       /*
+                        * Advance over all three lists simultaneously.
+                        *
+                        * Compare resultfiles[j] with expectfiles[j] always. Tags are
+                        * optional but if there are tags, the tag list has the same
+                        * length as the other two lists.
+                        */
+                       for (rl = resultfiles[i], el = expectfiles[i], tl = tags[i];
+                                rl != NULL;    /* rl and el have the same length */
+                                rl = rl->next, el = el->next)
                        {
-                               bool ignore = false;
+                               bool            newdiff;
+
+                               if (tl)
+                                       tl = tl->next;          /* tl has the same length as rl and el
+                                                                                * if it exists */
+
+                               newdiff = results_differ(tests[i], rl->str, el->str);
+                               if (newdiff && tl)
+                               {
+                                       printf("%s ", tl->str);
+                               }
+                               differ |= newdiff;
+                       }
+
+                       if (differ)
+                       {
+                               bool            ignore = false;
                                _stringlist *sl;
 
                                for (sl = ignorelist; sl != NULL; sl = sl->next)
@@ -1169,6 +1648,9 @@ run_schedule(const char *schedule)
                                success_count++;
                        }
 
+                       if (statuses[i] != 0)
+                               log_child_failure(statuses[i]);
+
                        status_end();
                }
        }
@@ -1180,15 +1662,48 @@ run_schedule(const char *schedule)
  * Run a single test
  */
 static void
-run_single_test(const char *test)
+run_single_test(const char *test, test_function tfunc)
 {
-       PID_TYPE pid;
+       PID_TYPE        pid;
+       int                     exit_status;
+       _stringlist *resultfiles = NULL;
+       _stringlist *expectfiles = NULL;
+       _stringlist *tags = NULL;
+       _stringlist *rl,
+                          *el,
+                          *tl;
+       bool            differ = false;
 
        status(_("test %-20s ... "), test);
-       pid = psql_start_test(test);
-       wait_for_tests(&pid, NULL, 1);
+       pid = (tfunc) (test, &resultfiles, &expectfiles, &tags);
+       wait_for_tests(&pid, &exit_status, NULL, 1);
+
+       /*
+        * Advance over all three lists simultaneously.
+        *
+        * Compare resultfiles[j] with expectfiles[j] always. Tags are optional
+        * but if there are tags, the tag list has the same length as the other
+        * two lists.
+        */
+       for (rl = resultfiles, el = expectfiles, tl = tags;
+                rl != NULL;                    /* rl and el have the same length */
+                rl = rl->next, el = el->next)
+       {
+               bool            newdiff;
+
+               if (tl)
+                       tl = tl->next;          /* tl has the same length as rl and el if it
+                                                                * exists */
+
+               newdiff = results_differ(test, rl->str, el->str);
+               if (newdiff && tl)
+               {
+                       printf("%s ", tl->str);
+               }
+               differ |= newdiff;
+       }
 
-       if (results_differ(test))
+       if (differ)
        {
                status(_("FAILED"));
                fail_count++;
@@ -1198,6 +1713,10 @@ run_single_test(const char *test)
                status(_("ok"));
                success_count++;
        }
+
+       if (exit_status != 0)
+               log_child_failure(exit_status);
+
        status_end();
 }
 
@@ -1207,8 +1726,8 @@ run_single_test(const char *test)
 static void
 open_result_files(void)
 {
-       char file[MAXPGPATH];
-       FILE *difffile;
+       char            file[MAXPGPATH];
+       FILE       *difffile;
 
        /* create the log file (copy of running status output) */
        snprintf(file, sizeof(file), "%s/regression.out", outputdir);
@@ -1240,6 +1759,94 @@ open_result_files(void)
                make_directory(file);
 }
 
+static void
+drop_database_if_exists(const char *dbname)
+{
+       header(_("dropping database \"%s\""), dbname);
+       psql_command("postgres", "DROP DATABASE IF EXISTS \"%s\"", dbname);
+}
+
+static void
+create_database(const char *dbname)
+{
+       _stringlist *sl;
+
+       /*
+        * 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'%s", dbname, encoding,
+                                        (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : "");
+       else
+               psql_command("postgres", "CREATE DATABASE \"%s\" TEMPLATE=template0%s", dbname,
+                                        (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : "");
+       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';"
+                       "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';",
+                                dbname, dbname, dbname, dbname, dbname);
+
+       /*
+        * Install any requested procedural languages
+        */
+       for (sl = loadlanguage; sl != NULL; sl = sl->next)
+       {
+               header(_("installing %s"), sl->str);
+               psql_command(dbname, "CREATE LANGUAGE \"%s\"", sl->str);
+       }
+}
+
+static void
+drop_role_if_exists(const char *rolename)
+{
+       header(_("dropping role \"%s\""), rolename);
+       psql_command("postgres", "DROP ROLE IF EXISTS \"%s\"", rolename);
+}
+
+static void
+create_role(const char *rolename, const _stringlist * granted_dbs)
+{
+       header(_("creating role \"%s\""), rolename);
+       psql_command("postgres", "CREATE ROLE \"%s\" WITH LOGIN", rolename);
+       for (; granted_dbs != NULL; granted_dbs = granted_dbs->next)
+       {
+               psql_command("postgres", "GRANT ALL ON DATABASE \"%s\" TO \"%s\"",
+                                        granted_dbs->str, rolename);
+       }
+}
+
+static char *
+make_absolute_path(const char *in)
+{
+       char       *result;
+
+       if (is_absolute_path(in))
+               result = strdup(in);
+       else
+       {
+               static char cwdbuf[MAXPGPATH];
+
+               if (!cwdbuf[0])
+               {
+                       if (!getcwd(cwdbuf, sizeof(cwdbuf)))
+                       {
+                               fprintf(stderr, _("could not get current working directory: %s\n"), strerror(errno));
+                               exit_nicely(2);
+                       }
+               }
+
+               result = malloc(strlen(cwdbuf) + strlen(in) + 2);
+               sprintf(result, "%s/%s", cwdbuf, in);
+       }
+
+       canonicalize_path(result);
+       return result;
+}
+
 static void
 help(void)
 {
@@ -1253,18 +1860,21 @@ help(void)
        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(_("  --create-role=ROLE        create the specified role before testing\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(_("                            (can be used multiple times to concatenate)\n"));
+       printf(_("  --dlpath=DIR              look for dynamic libraries in DIR\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(_("  --no-locale               use C locale\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(_("  --port=PORT               start postmaster on PORT\n"));
+       printf(_("  --temp-config=PATH        append contents of PATH to temporary config\n"));
        printf(_("\n"));
        printf(_("Options for using an existing installation:\n"));
        printf(_("  --host=HOST               use postmaster running on HOST\n"));
@@ -1279,13 +1889,14 @@ help(void)
 }
 
 int
-main(int argc, char *argv[])
+regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc)
 {
        _stringlist *sl;
-       int c;
-       int i;
-       int option_index;
-       char buf[MAXPGPATH * 4];
+       int                     c;
+       int                     i;
+       int                     option_index;
+       char            buf[MAXPGPATH * 4];
+       char            buf2[MAXPGPATH * 4];
 
        static struct option long_options[] = {
                {"help", no_argument, NULL, 'h'},
@@ -1301,22 +1912,31 @@ main(int argc, char *argv[])
                {"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},
                {"psqldir", required_argument, NULL, 16},
+               {"dlpath", required_argument, NULL, 17},
+               {"create-role", required_argument, NULL, 18},
+               {"temp-config", required_argument, NULL, 19},
+               {"use-existing", no_argument, NULL, 20},
                {NULL, 0, NULL, 0}
        };
 
        progname = get_progname(argv[0]);
-       set_pglocale_pgservice(argv[0], "pg_regress");
+       set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_regress"));
 
 #ifndef HAVE_UNIX_SOCKETS
        /* no unix domain sockets available, so change default */
        hostname = "localhost";
 #endif
 
+       /*
+        * We call the initialization function here because that way we can set
+        * default parameters and let them be overwritten by the commandline.
+        */
+       ifunc();
+
        while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1)
        {
                switch (c)
@@ -1325,10 +1945,16 @@ main(int argc, char *argv[])
                                help();
                                exit_nicely(0);
                        case 'V':
-                               printf("pg_regress (PostgreSQL %s)\n", PG_VERSION);
+                               puts("pg_regress (PostgreSQL) " PG_VERSION);
                                exit_nicely(0);
                        case 1:
-                               dbname = strdup(optarg);
+
+                               /*
+                                * If a default database was specified, we need to remove it
+                                * before we add the specified one.
+                                */
+                               free_stringlist(&dblist);
+                               split_to_stringlist(strdup(optarg), ", ", &dblist);
                                break;
                        case 2:
                                debug = true;
@@ -1352,22 +1978,7 @@ main(int argc, char *argv[])
                                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);
+                               temp_install = make_absolute_path(optarg);
                                break;
                        case 10:
                                nolocale = true;
@@ -1375,20 +1986,12 @@ main(int argc, char *argv[])
                        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);
+                               port_specified_by_user = true;
                                break;
                        case 15:
                                user = strdup(optarg);
@@ -1398,6 +2001,18 @@ main(int argc, char *argv[])
                                if (strlen(optarg))
                                        psqldir = strdup(optarg);
                                break;
+                       case 17:
+                               dlpath = strdup(optarg);
+                               break;
+                       case 18:
+                               split_to_stringlist(strdup(optarg), ", ", &extraroles);
+                               break;
+                       case 19:
+                               temp_config = strdup(optarg);
+                               break;
+                       case 20:
+                               use_existing = true;
+                               break;
                        default:
                                /* getopt_long already emitted a complaint */
                                fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
@@ -1415,8 +2030,18 @@ main(int argc, char *argv[])
                optind++;
        }
 
-       if (temp_install)
-               port = temp_port;
+       if (temp_install && !port_specified_by_user)
+
+               /*
+                * To reduce chances of interference with parallel installations, use
+                * a port number starting in the private range (49152-65535)
+                * calculated from the version number.
+                */
+               port = 0xC000 | (PG_VERSION_NUM & 0x3FFF);
+
+       inputdir = make_absolute_path(inputdir);
+       outputdir = make_absolute_path(outputdir);
+       dlpath = make_absolute_path(dlpath);
 
        /*
         * Initialization
@@ -1425,8 +2050,14 @@ main(int argc, char *argv[])
 
        initialize_environment();
 
+#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
+       unlimit_core_size();
+#endif
+
        if (temp_install)
        {
+               FILE       *pg_conf;
+
                /*
                 * Prepare the temp installation
                 */
@@ -1439,7 +2070,7 @@ main(int argc, char *argv[])
                if (directory_exists(temp_install))
                {
                        header(_("removing existing temp installation"));
-                       rmtree(temp_install,true);
+                       rmtree(temp_install, true);
                }
 
                header(_("creating temporary installation"));
@@ -1453,9 +2084,15 @@ main(int argc, char *argv[])
                        make_directory(buf);
 
                /* "make install" */
+#ifndef WIN32_ONLY_COMPILER
                snprintf(buf, sizeof(buf),
                                 SYSTEMQUOTE "\"%s\" -C \"%s\" DESTDIR=\"%s/install\" install with_perl=no with_python=no > \"%s/log/install.log\" 2>&1" SYSTEMQUOTE,
                                 makeprog, top_builddir, temp_install, outputdir);
+#else
+               snprintf(buf, sizeof(buf),
+                                SYSTEMQUOTE "perl \"%s/src/tools/msvc/install.pl\" \"%s/install\" >\"%s/log/install.log\" 2>&1" SYSTEMQUOTE,
+                                top_builddir, temp_install, outputdir);
+#endif
                if (system(buf))
                {
                        fprintf(stderr, _("\n%s: installation failed\nExamine %s/log/install.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
@@ -1476,12 +2113,79 @@ main(int argc, char *argv[])
                        exit_nicely(2);
                }
 
+               /*
+                * Adjust the default postgresql.conf as needed for regression
+                * testing. The user can specify a file to be appended; in any case we
+                * set max_prepared_transactions to enable testing of prepared xacts.
+                * (Note: to reduce the probability of unexpected shmmax failures,
+                * don't set max_prepared_transactions any higher than actually needed
+                * by the prepared_xacts regression test.)
+                */
+               snprintf(buf, sizeof(buf), "%s/data/postgresql.conf", temp_install);
+               pg_conf = fopen(buf, "a");
+               if (pg_conf == NULL)
+               {
+                       fprintf(stderr, _("\n%s: could not open \"%s\" for adding extra config: %s\n"), progname, buf, strerror(errno));
+                       exit_nicely(2);
+               }
+               fputs("\n# Configuration added by pg_regress\n\n", pg_conf);
+               fputs("max_prepared_transactions = 2\n", pg_conf);
+
+               if (temp_config != NULL)
+               {
+                       FILE       *extra_conf;
+                       char            line_buf[1024];
+
+                       extra_conf = fopen(temp_config, "r");
+                       if (extra_conf == NULL)
+                       {
+                               fprintf(stderr, _("\n%s: could not open \"%s\" to read extra config: %s\n"), progname, temp_config, strerror(errno));
+                               exit_nicely(2);
+                       }
+                       while (fgets(line_buf, sizeof(line_buf), extra_conf) != NULL)
+                               fputs(line_buf, pg_conf);
+                       fclose(extra_conf);
+               }
+
+               fclose(pg_conf);
+
+               /*
+                * Check if there is a postmaster running already.
+                */
+               snprintf(buf2, sizeof(buf2),
+                                SYSTEMQUOTE "\"%s/psql\" -X postgres <%s 2>%s" SYSTEMQUOTE,
+                                bindir, DEVNULL, DEVNULL);
+
+               for (i = 0; i < 16; i++)
+               {
+                       if (system(buf2) == 0)
+                       {
+                               char            s[16];
+
+                               if (port_specified_by_user || i == 15)
+                               {
+                                       fprintf(stderr, _("port %d apparently in use\n"), port);
+                                       if (!port_specified_by_user)
+                                               fprintf(stderr, _("%s: could not determine an available port\n"), progname);
+                                       fprintf(stderr, _("Specify an unused port using the --port option or shut down any conflicting PostgreSQL servers.\n"));
+                                       exit_nicely(2);
+                               }
+
+                               fprintf(stderr, _("port %d apparently in use, trying %d\n"), port, port + 1);
+                               port++;
+                               sprintf(s, "%d", port);
+                               doputenv("PGPORT", s);
+                       }
+                       else
+                               break;
+               }
+
                /*
                 * Start the temp postmaster
                 */
                header(_("starting postmaster"));
                snprintf(buf, sizeof(buf),
-                                SYSTEMQUOTE "\"%s/postmaster\" -D \"%s/data\" -F%s -c \"listen_addresses=%s\" > \"%s/log/postmaster.log\" 2>&1" SYSTEMQUOTE,
+                                SYSTEMQUOTE "\"%s/postgres\" -D \"%s/data\" -F%s -c \"listen_addresses=%s\" > \"%s/log/postmaster.log\" 2>&1" SYSTEMQUOTE,
                                 bindir, temp_install,
                                 debug ? " -d 5" : "",
                                 hostname ? hostname : "",
@@ -1495,85 +2199,84 @@ main(int argc, char *argv[])
                }
 
                /*
-                * 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.
+                * 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),
-                                SYSTEMQUOTE "\"%s/psql\" -X postgres <%s 2>%s" SYSTEMQUOTE,
-                                bindir, DEVNULL, DEVNULL);
                for (i = 0; i < 60; i++)
                {
                        /* Done if psql succeeds */
-                       if (system(buf) == 0)
+                       if (system(buf2) == 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)
+#else
+                       if (WaitForSingleObject(postmaster_pid, 0) == WAIT_OBJECT_0)
+#endif
                        {
                                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)
+               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);
+                       fprintf(stderr, _("\n%s: postmaster did not respond within 60 seconds\nExamine %s/log/postmaster.log for the reason\n"), progname, outputdir);
+
+                       /*
+                        * If we get here, the postmaster is probably wedged somewhere in
+                        * startup.  Try to kill it ungracefully rather than leaving a
+                        * stuck postmaster that might interfere with subsequent test
+                        * attempts.
+                        */
+#ifndef WIN32
+                       if (kill(postmaster_pid, SIGKILL) != 0 &&
+                               errno != ESRCH)
+                               fprintf(stderr, _("\n%s: could not kill failed postmaster: %s\n"),
+                                               progname, strerror(errno));
+#else
+                       if (TerminateProcess(postmaster_pid, 255) == 0)
+                               fprintf(stderr, _("\n%s: could not kill failed postmaster: %lu\n"),
+                                               progname, GetLastError());
+#endif
+
                        exit_nicely(2);
                }
 
                postmaster_running = true;
 
                printf(_("running on port %d with pid %lu\n"),
-                          temp_port, (unsigned long) postmaster_pid);
+                          port, (unsigned long) postmaster_pid);
        }
        else
        {
                /*
                 * Using an existing installation, so may need to get rid of
-                * pre-existing database.
+                * pre-existing database(s) and role(s)
                 */
-               header(_("dropping database \"%s\""), dbname);
-               psql_command("postgres","DROP DATABASE IF EXISTS \"%s\"", dbname);
+               if (!use_existing)
+               {
+                       for (sl = dblist; sl; sl = sl->next)
+                               drop_database_if_exists(sl->str);
+                       for (sl = extraroles; sl; sl = sl->next)
+                               drop_role_if_exists(sl->str);
+               }
        }
 
        /*
-        * Create the test database
-        *
-        * We use template0 so that any installation-local cruft in template1
-        * will not mess up the tests.
+        * Create the test database(s) and role(s)
         */
-       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)
+       if (!use_existing)
        {
-               header(_("installing %s"), sl->str);
-               psql_command(dbname, "CREATE LANGUAGE \"%s\"", sl->str);
+               for (sl = dblist; sl; sl = sl->next)
+                       create_database(sl->str);
+               for (sl = extraroles; sl; sl = sl->next)
+                       create_role(sl->str, dblist);
        }
 
        /*
@@ -1583,12 +2286,12 @@ main(int argc, char *argv[])
 
        for (sl = schedulelist; sl != NULL; sl = sl->next)
        {
-               run_schedule(sl->str);
+               run_schedule(sl->str, tfunc);
        }
 
        for (sl = extra_tests; sl != NULL; sl = sl->next)
        {
-               run_single_test(sl->str);
+               run_single_test(sl->str, tfunc);
        }
 
        /*
@@ -1609,22 +2312,23 @@ main(int argc, char *argv[])
                snprintf(buf, sizeof(buf),
                                 _(" All %d tests passed. "),
                                 success_count);
-       else if (fail_count == 0) /* fail_count=0, fail_ignore_count>0 */
+       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 */
+       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 */
+                                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_count + fail_ignore_count,
+                                success_count + fail_count + fail_ignore_count,
                                 fail_ignore_count);
 
        putchar('\n');