summaryrefslogtreecommitdiffstats
path: root/src/test/regress/pg_regress.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/regress/pg_regress.c')
-rw-r--r--src/test/regress/pg_regress.c2697
1 files changed, 2697 insertions, 0 deletions
diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c
new file mode 100644
index 0000000..27486cf
--- /dev/null
+++ b/src/test/regress/pg_regress.c
@@ -0,0 +1,2697 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_regress --- regression test driver
+ *
+ * This is a C implementation of the previous shell script for running
+ * the regression tests, and should be mostly 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-2021, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/test/regress/pg_regress.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <signal.h>
+#include <unistd.h>
+
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+
+#include "common/logging.h"
+#include "common/restricted_token.h"
+#include "common/string.h"
+#include "common/username.h"
+#include "getopt_long.h"
+#include "lib/stringinfo.h"
+#include "libpq/pqcomm.h" /* needed for UNIXSOCK_PATH() */
+#include "pg_config_paths.h"
+#include "pg_regress.h"
+#include "portability/instr_time.h"
+
+/* for resultmap we need a list of pairs of strings */
+typedef struct _resultmap
+{
+ char *test;
+ char *type;
+ char *resultfile;
+ struct _resultmap *next;
+} _resultmap;
+
+/*
+ * Values obtained from Makefile.
+ */
+char *host_platform = HOST_TUPLE;
+
+#ifndef WIN32 /* not used in WIN32 case */
+static char *shellprog = SHELLPROG;
+#endif
+
+/*
+ * 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 = "-U3";
+#else
+const char *basic_diff_opts = "-w";
+const char *pretty_diff_opts = "-w -U3";
+#endif
+
+/* options settable from command line */
+_stringlist *dblist = NULL;
+bool debug = false;
+char *inputdir = ".";
+char *outputdir = ".";
+char *bindir = PGBINDIR;
+char *launcher = NULL;
+static _stringlist *loadextension = NULL;
+static int max_connections = 0;
+static int max_concurrent_tests = 0;
+static char *encoding = NULL;
+static _stringlist *schedulelist = NULL;
+static _stringlist *extra_tests = NULL;
+static char *temp_instance = NULL;
+static _stringlist *temp_configs = NULL;
+static bool nolocale = false;
+static bool use_existing = false;
+static char *hostname = NULL;
+static int port = -1;
+static bool port_specified_by_user = false;
+static char *dlpath = PKGLIBDIR;
+static char *user = NULL;
+static _stringlist *extraroles = NULL;
+static char *config_auth_datadir = NULL;
+
+/* internal variables */
+static const char *progname;
+static char *logfilename;
+static FILE *logfile;
+static char *difffilename;
+static const char *sockdir;
+#ifdef HAVE_UNIX_SOCKETS
+static const char *temp_sockdir;
+static char sockself[MAXPGPATH];
+static char socklock[MAXPGPATH];
+#endif
+
+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 bool directory_exists(const char *dir);
+static void make_directory(const char *dir);
+
+static void header(const char *fmt,...) pg_attribute_printf(1, 2);
+static void status(const char *fmt,...) pg_attribute_printf(1, 2);
+static void psql_command(const char *database, const char *query,...) pg_attribute_printf(2, 3);
+
+/*
+ * allow core files if possible.
+ */
+#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
+static void
+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 = pg_malloc(sizeof(_stringlist));
+ _stringlist *oldentry;
+
+ newentry->str = pg_strdup(str);
+ newentry->next = NULL;
+ if (*listhead == NULL)
+ *listhead = newentry;
+ else
+ {
+ for (oldentry = *listhead; oldentry->next; oldentry = oldentry->next)
+ /* 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 = pg_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.
+ */
+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];
+ int r;
+
+ /* On Windows, system() seems not to force fflush, so... */
+ fflush(stdout);
+ fflush(stderr);
+
+ snprintf(buf, sizeof(buf),
+ "\"%s%spg_ctl\" stop -D \"%s/data\" -s",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ temp_instance);
+ 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(), that could be recursive */
+ }
+
+ postmaster_running = false;
+ }
+}
+
+#ifdef HAVE_UNIX_SOCKETS
+/*
+ * Remove the socket temporary directory. pg_regress never waits for a
+ * postmaster exit, so it is indeterminate whether the postmaster has yet to
+ * unlink the socket and lock file. Unlink them here so we can proceed to
+ * remove the directory. Ignore errors; leaking a temporary directory is
+ * unimportant. This can run from a signal handler. The code is not
+ * acceptable in a Windows signal handler (see initdb.c:trapsig()), but
+ * on Windows, pg_regress does not use Unix sockets by default.
+ */
+static void
+remove_temp(void)
+{
+ Assert(temp_sockdir);
+ unlink(sockself);
+ unlink(socklock);
+ rmdir(temp_sockdir);
+}
+
+/*
+ * Signal handler that calls remove_temp() and reraises the signal.
+ */
+static void
+signal_remove_temp(int signum)
+{
+ remove_temp();
+
+ pqsignal(signum, SIG_DFL);
+ raise(signum);
+}
+
+/*
+ * Create a temporary directory suitable for the server's Unix-domain socket.
+ * The directory will have mode 0700 or stricter, so no other OS user can open
+ * our socket to exploit our use of trust authentication. Most systems
+ * constrain the length of socket paths well below _POSIX_PATH_MAX, so we
+ * place the directory under /tmp rather than relative to the possibly-deep
+ * current working directory.
+ *
+ * Compared to using the compiled-in DEFAULT_PGSOCKET_DIR, this also permits
+ * testing to work in builds that relocate it to a directory not writable to
+ * the build/test user.
+ */
+static const char *
+make_temp_sockdir(void)
+{
+ char *template = psprintf("%s/pg_regress-XXXXXX",
+ getenv("TMPDIR") ? getenv("TMPDIR") : "/tmp");
+
+ temp_sockdir = mkdtemp(template);
+ if (temp_sockdir == NULL)
+ {
+ fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
+ progname, template, strerror(errno));
+ exit(2);
+ }
+
+ /* Stage file names for remove_temp(). Unsafe in a signal handler. */
+ UNIXSOCK_PATH(sockself, port, temp_sockdir);
+ snprintf(socklock, sizeof(socklock), "%s.lock", sockself);
+
+ /* Remove the directory during clean exit. */
+ atexit(remove_temp);
+
+ /*
+ * Remove the directory before dying to the usual signals. Omit SIGQUIT,
+ * preserving it as a quick, untidy exit.
+ */
+ pqsignal(SIGHUP, signal_remove_temp);
+ pqsignal(SIGINT, signal_remove_temp);
+ pqsignal(SIGPIPE, signal_remove_temp);
+ pqsignal(SIGTERM, signal_remove_temp);
+
+ return temp_sockdir;
+}
+#endif /* HAVE_UNIX_SOCKETS */
+
+/*
+ * 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;
+}
+
+/*
+ * Replace all occurrences of "replace" in "string" with "replacement".
+ * The StringInfo will be suitably enlarged if necessary.
+ *
+ * Note: this is optimized on the assumption that most calls will find
+ * no more than one occurrence of "replace", and quite likely none.
+ */
+void
+replace_string(StringInfo string, const char *replace, const char *replacement)
+{
+ int pos = 0;
+ char *ptr;
+
+ while ((ptr = strstr(string->data + pos, replace)) != NULL)
+ {
+ /* Must copy the remainder of the string out of the StringInfo */
+ char *suffix = pg_strdup(ptr + strlen(replace));
+
+ /* Truncate StringInfo at start of found string ... */
+ string->len = ptr - string->data;
+ /* ... and append the replacement (this restores the trailing '\0') */
+ appendStringInfoString(string, replacement);
+ /* Next search should start after the replacement */
+ pos = string->len;
+ /* Put back the remainder of the string */
+ appendStringInfoString(string, suffix);
+ free(suffix);
+ }
+}
+
+/*
+ * 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(const char *source_subdir, const char *dest_dir, const char *dest_subdir, const char *suffix)
+{
+ char testtablespace[MAXPGPATH];
+ char indir[MAXPGPATH];
+ char outdir_sub[MAXPGPATH];
+ char **name;
+ char **names;
+ int count = 0;
+
+ snprintf(indir, MAXPGPATH, "%s/%s", inputdir, source_subdir);
+
+ /* Check that indir actually exists and is a directory */
+ if (!directory_exists(indir))
+ {
+ /*
+ * 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(2);
+
+ /* Create the "dest" subdirectory if not present */
+ snprintf(outdir_sub, MAXPGPATH, "%s/%s", dest_dir, dest_subdir);
+ if (!directory_exists(outdir_sub))
+ make_directory(outdir_sub);
+
+ /* We might need to replace @testtablespace@ */
+ snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir);
+
+ /* 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;
+ StringInfoData line;
+
+ /* 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.%s", dest_dir, 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(2);
+ }
+ outfile = fopen(destfile, "w");
+ if (!outfile)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, destfile, strerror(errno));
+ exit(2);
+ }
+
+ initStringInfo(&line);
+
+ while (pg_get_line_buf(infile, &line))
+ {
+ 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.data, outfile);
+ }
+
+ pfree(line.data);
+ 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(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", outputdir, "sql", "sql");
+ convert_sourcefiles_in("output", outputdir, "expected", "out");
+}
+
+/*
+ * Clean out the test tablespace dir, or create it if it doesn't exist.
+ *
+ * On Windows, doing this cleanup here makes it possible to run the
+ * regression tests under a Windows administrative user account with the
+ * restricted token obtained when starting pg_regress.
+ */
+static void
+prepare_testtablespace_dir(void)
+{
+ char testtablespace[MAXPGPATH];
+
+ snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir);
+
+ if (directory_exists(testtablespace))
+ {
+ if (!rmtree(testtablespace, true))
+ {
+ fprintf(stderr, _("\n%s: could not remove test tablespace \"%s\"\n"),
+ progname, testtablespace);
+ exit(2);
+ }
+ }
+ make_directory(testtablespace);
+}
+
+/*
+ * 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(2);
+ }
+
+ while (fgets(buf, sizeof(buf), f))
+ {
+ 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]))
+ buf[--i] = '\0';
+
+ /* parse out the line fields */
+ file_type = strchr(buf, ':');
+ if (!file_type)
+ {
+ fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
+ buf);
+ exit(2);
+ }
+ *file_type++ = '\0';
+
+ platform = strchr(file_type, ':');
+ if (!platform)
+ {
+ fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
+ buf);
+ exit(2);
+ }
+ *platform++ = '\0';
+ expected = strchr(platform, '=');
+ if (!expected)
+ {
+ fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
+ buf);
+ exit(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 = pg_malloc(sizeof(_resultmap));
+
+ entry->test = pg_strdup(buf);
+ entry->type = pg_strdup(file_type);
+ entry->resultfile = pg_strdup(expected);
+ entry->next = resultmap;
+ resultmap = entry;
+ }
+ }
+ 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;
+}
+
+/*
+ * Prepare environment variables for running regression tests
+ */
+static void
+initialize_environment(void)
+{
+ /*
+ * Set default application_name. (The test_start_function may choose to
+ * override this, but if it doesn't, we have something useful in place.)
+ */
+ setenv("PGAPPNAME", "pg_regress", 1);
+
+ 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");
+
+ /*
+ * Most platforms have adopted the POSIX locale as their
+ * implementation-defined default locale. Exceptions include native
+ * Windows, macOS with --enable-nls, and Cygwin with --enable-nls.
+ * (Use of --enable-nls matters because libintl replaces setlocale().)
+ * Also, PostgreSQL does not support macOS with locale environment
+ * variables unset; see PostmasterMain().
+ */
+#if defined(WIN32) || defined(__CYGWIN__) || defined(__darwin__)
+ setenv("LANG", "C", 1);
+#endif
+ }
+
+ /*
+ * 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("LANGUAGE");
+ unsetenv("LC_ALL");
+ setenv("LC_MESSAGES", "C", 1);
+
+ /*
+ * Set encoding as requested
+ */
+ if (encoding)
+ setenv("PGCLIENTENCODING", encoding, 1);
+ else
+ unsetenv("PGCLIENTENCODING");
+
+ /*
+ * Set timezone and datestyle for datetime-related tests
+ */
+ setenv("PGTZ", "PST8PDT", 1);
+ setenv("PGDATESTYLE", "Postgres, MDY", 1);
+
+ /*
+ * 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 = psprintf("%s %s",
+ old_pgoptions, my_pgoptions);
+ setenv("PGOPTIONS", new_pgoptions, 1);
+ free(new_pgoptions);
+ }
+
+ if (temp_instance)
+ {
+ /*
+ * 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
+ * PGHOST depending on whether we are using TCP or Unix sockets.
+ *
+ * This list should be kept in sync with TestLib.pm.
+ */
+ unsetenv("PGCHANNELBINDING");
+ /* PGCLIENTENCODING, see above */
+ unsetenv("PGCONNECT_TIMEOUT");
+ unsetenv("PGDATA");
+ unsetenv("PGDATABASE");
+ unsetenv("PGGSSENCMODE");
+ unsetenv("PGGSSLIB");
+ /* PGHOSTADDR, see below */
+ unsetenv("PGKRBSRVNAME");
+ unsetenv("PGPASSFILE");
+ unsetenv("PGPASSWORD");
+ unsetenv("PGREQUIREPEER");
+ unsetenv("PGREQUIRESSL");
+ unsetenv("PGSERVICE");
+ unsetenv("PGSERVICEFILE");
+ unsetenv("PGSSLCERT");
+ unsetenv("PGSSLCRL");
+ unsetenv("PGSSLCRLDIR");
+ unsetenv("PGSSLKEY");
+ unsetenv("PGSSLMAXPROTOCOLVERSION");
+ unsetenv("PGSSLMINPROTOCOLVERSION");
+ unsetenv("PGSSLMODE");
+ unsetenv("PGSSLROOTCERT");
+ unsetenv("PGSSLSNI");
+ unsetenv("PGTARGETSESSIONATTRS");
+ unsetenv("PGUSER");
+ /* PGPORT, see below */
+ /* PGHOST, see below */
+
+#ifdef HAVE_UNIX_SOCKETS
+ if (hostname != NULL)
+ setenv("PGHOST", hostname, 1);
+ else
+ {
+ sockdir = getenv("PG_REGRESS_SOCK_DIR");
+ if (!sockdir)
+ sockdir = make_temp_sockdir();
+ setenv("PGHOST", sockdir, 1);
+ }
+#else
+ Assert(hostname != NULL);
+ setenv("PGHOST", hostname, 1);
+#endif
+ unsetenv("PGHOSTADDR");
+ if (port != -1)
+ {
+ char s[16];
+
+ sprintf(s, "%d", port);
+ setenv("PGPORT", s, 1);
+ }
+ }
+ 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)
+ {
+ setenv("PGHOST", hostname, 1);
+ unsetenv("PGHOSTADDR");
+ }
+ if (port != -1)
+ {
+ char s[16];
+
+ sprintf(s, "%d", port);
+ setenv("PGPORT", s, 1);
+ }
+ if (user != NULL)
+ setenv("PGUSER", user, 1);
+
+ /*
+ * However, we *don't* honor PGDATABASE, since we certainly don't wish
+ * to connect to whatever database the user might like as default.
+ * (Most tests override PGDATABASE anyway, but there are some ECPG
+ * test cases that don't.)
+ */
+ unsetenv("PGDATABASE");
+
+ /*
+ * Report what we're connecting to
+ */
+ pghost = getenv("PGHOST");
+ pgport = getenv("PGPORT");
+ if (!pghost)
+ {
+ /* Keep this bit in sync with libpq's default host location: */
+#ifdef HAVE_UNIX_SOCKETS
+ if (DEFAULT_PGSOCKET_DIR[0])
+ /* do nothing, we'll print "Unix socket" below */ ;
+ else
+#endif
+ pghost = "localhost"; /* DefaultHost in fe-connect.c */
+ }
+
+ 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"));
+ }
+
+ convert_sourcefiles();
+ load_resultmap();
+}
+
+#ifdef ENABLE_SSPI
+
+/* support for config_sspi_auth() */
+static const char *
+fmtHba(const char *raw)
+{
+ static char *ret;
+ const char *rp;
+ char *wp;
+
+ wp = ret = realloc(ret, 3 + strlen(raw) * 2);
+
+ *wp++ = '"';
+ for (rp = raw; *rp; rp++)
+ {
+ if (*rp == '"')
+ *wp++ = '"';
+ *wp++ = *rp;
+ }
+ *wp++ = '"';
+ *wp++ = '\0';
+
+ return ret;
+}
+
+/*
+ * Get account and domain/realm names for the current user. This is based on
+ * pg_SSPI_recvauth(). The returned strings use static storage.
+ */
+static void
+current_windows_user(const char **acct, const char **dom)
+{
+ static char accountname[MAXPGPATH];
+ static char domainname[MAXPGPATH];
+ HANDLE token;
+ TOKEN_USER *tokenuser;
+ DWORD retlen;
+ DWORD accountnamesize = sizeof(accountname);
+ DWORD domainnamesize = sizeof(domainname);
+ SID_NAME_USE accountnameuse;
+
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_READ, &token))
+ {
+ fprintf(stderr,
+ _("%s: could not open process token: error code %lu\n"),
+ progname, GetLastError());
+ exit(2);
+ }
+
+ if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
+ {
+ fprintf(stderr,
+ _("%s: could not get token information buffer size: error code %lu\n"),
+ progname, GetLastError());
+ exit(2);
+ }
+ tokenuser = pg_malloc(retlen);
+ if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
+ {
+ fprintf(stderr,
+ _("%s: could not get token information: error code %lu\n"),
+ progname, GetLastError());
+ exit(2);
+ }
+
+ if (!LookupAccountSid(NULL, tokenuser->User.Sid, accountname, &accountnamesize,
+ domainname, &domainnamesize, &accountnameuse))
+ {
+ fprintf(stderr,
+ _("%s: could not look up account SID: error code %lu\n"),
+ progname, GetLastError());
+ exit(2);
+ }
+
+ free(tokenuser);
+
+ *acct = accountname;
+ *dom = domainname;
+}
+
+/*
+ * Rewrite pg_hba.conf and pg_ident.conf to use SSPI authentication. Permit
+ * the current OS user to authenticate as the bootstrap superuser and as any
+ * user named in a --create-role option.
+ *
+ * In --config-auth mode, the --user switch can be used to specify the
+ * bootstrap superuser's name, otherwise we assume it is the default.
+ */
+static void
+config_sspi_auth(const char *pgdata, const char *superuser_name)
+{
+ const char *accountname,
+ *domainname;
+ char *errstr;
+ bool have_ipv6;
+ char fname[MAXPGPATH];
+ int res;
+ FILE *hba,
+ *ident;
+ _stringlist *sl;
+
+ /* Find out the name of the current OS user */
+ current_windows_user(&accountname, &domainname);
+
+ /* Determine the bootstrap superuser's name */
+ if (superuser_name == NULL)
+ {
+ /*
+ * Compute the default superuser name the same way initdb does.
+ *
+ * It's possible that this result always matches "accountname", the
+ * value SSPI authentication discovers. But the underlying system
+ * functions do not clearly guarantee that.
+ */
+ superuser_name = get_user_name(&errstr);
+ if (superuser_name == NULL)
+ {
+ fprintf(stderr, "%s: %s\n", progname, errstr);
+ exit(2);
+ }
+ }
+
+ /*
+ * Like initdb.c:setup_config(), determine whether the platform recognizes
+ * ::1 (IPv6 loopback) as a numeric host address string.
+ */
+ {
+ struct addrinfo *gai_result;
+ struct addrinfo hints;
+ WSADATA wsaData;
+
+ hints.ai_flags = AI_NUMERICHOST;
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = 0;
+ hints.ai_protocol = 0;
+ hints.ai_addrlen = 0;
+ hints.ai_canonname = NULL;
+ hints.ai_addr = NULL;
+ hints.ai_next = NULL;
+
+ have_ipv6 = (WSAStartup(MAKEWORD(2, 2), &wsaData) == 0 &&
+ getaddrinfo("::1", NULL, &hints, &gai_result) == 0);
+ }
+
+ /* Check a Write outcome and report any error. */
+#define CW(cond) \
+ do { \
+ if (!(cond)) \
+ { \
+ fprintf(stderr, _("%s: could not write to file \"%s\": %s\n"), \
+ progname, fname, strerror(errno)); \
+ exit(2); \
+ } \
+ } while (0)
+
+ res = snprintf(fname, sizeof(fname), "%s/pg_hba.conf", pgdata);
+ if (res < 0 || res >= sizeof(fname))
+ {
+ /*
+ * Truncating this name is a fatal error, because we must not fail to
+ * overwrite an original trust-authentication pg_hba.conf.
+ */
+ fprintf(stderr, _("%s: directory name too long\n"), progname);
+ exit(2);
+ }
+ hba = fopen(fname, "w");
+ if (hba == NULL)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, fname, strerror(errno));
+ exit(2);
+ }
+ CW(fputs("# Configuration written by config_sspi_auth()\n", hba) >= 0);
+ CW(fputs("host all all 127.0.0.1/32 sspi include_realm=1 map=regress\n",
+ hba) >= 0);
+ if (have_ipv6)
+ CW(fputs("host all all ::1/128 sspi include_realm=1 map=regress\n",
+ hba) >= 0);
+ CW(fclose(hba) == 0);
+
+ snprintf(fname, sizeof(fname), "%s/pg_ident.conf", pgdata);
+ ident = fopen(fname, "w");
+ if (ident == NULL)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
+ progname, fname, strerror(errno));
+ exit(2);
+ }
+ CW(fputs("# Configuration written by config_sspi_auth()\n", ident) >= 0);
+
+ /*
+ * Double-quote for the benefit of account names containing whitespace or
+ * '#'. Windows forbids the double-quote character itself, so don't
+ * bother escaping embedded double-quote characters.
+ */
+ CW(fprintf(ident, "regress \"%s@%s\" %s\n",
+ accountname, domainname, fmtHba(superuser_name)) >= 0);
+ for (sl = extraroles; sl; sl = sl->next)
+ CW(fprintf(ident, "regress \"%s@%s\" %s\n",
+ accountname, domainname, fmtHba(sl->str)) >= 0);
+ CW(fclose(ident) == 0);
+}
+
+#endif /* ENABLE_SSPI */
+
+/*
+ * 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%spsql\" -X -c \"%s\" \"%s\"",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ query_escaped,
+ database);
+
+ if (system(psql_cmd) != 0)
+ {
+ /* psql probably already reported the error */
+ fprintf(stderr, _("command failed: %s\n"), psql_cmd);
+ exit(2);
+ }
+}
+
+/*
+ * Spawn a process to execute the given shell command; don't wait for it
+ *
+ * Returns the process ID (or HANDLE) so we can wait for it later
+ */
+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(2);
+ }
+ if (pid == 0)
+ {
+ /*
+ * 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.
+ */
+ char *cmdline2;
+
+ cmdline2 = psprintf("exec %s", cmdline);
+ execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL);
+ fprintf(stderr, _("%s: could not exec \"%s\": %s\n"),
+ progname, shellprog, strerror(errno));
+ _exit(1); /* not exit() here... */
+ }
+ /* in parent */
+ return pid;
+#else
+ PROCESS_INFORMATION pi;
+ char *cmdline2;
+ HANDLE restrictedToken;
+ const char *comspec;
+
+ /* Find CMD.EXE location using COMSPEC, if it's set */
+ comspec = getenv("COMSPEC");
+ if (comspec == NULL)
+ comspec = "CMD";
+
+ memset(&pi, 0, sizeof(pi));
+ cmdline2 = psprintf("\"%s\" /c \"%s\"", comspec, cmdline);
+
+ if ((restrictedToken =
+ CreateRestrictedProcess(cmdline2, &pi)) == 0)
+ exit(2);
+
+ CloseHandle(pi.hThread);
+ return pi.hProcess;
+#endif
+}
+
+/*
+ * 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;
+}
+
+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 (S_ISDIR(st.st_mode))
+ return true;
+ return false;
+}
+
+/* Create a directory */
+static void
+make_directory(const char *dir)
+{
+ if (mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO) < 0)
+ {
+ fprintf(stderr, _("%s: could not create directory \"%s\": %s\n"),
+ progname, dir, strerror(errno));
+ exit(2);
+ }
+}
+
+/*
+ * In: filename.ext, Return: filename_i.ext, where 0 < i <= 9
+ */
+static char *
+get_alternative_expectfile(const char *expectfile, int i)
+{
+ char *last_dot;
+ int ssize = strlen(expectfile) + 2 + 1;
+ char *tmp;
+ char *s;
+
+ if (!(tmp = (char *) malloc(ssize)))
+ return NULL;
+
+ if (!(s = (char *) malloc(ssize)))
+ {
+ free(tmp);
+ return NULL;
+ }
+
+ 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);
+ if (!WIFEXITED(r) || WEXITSTATUS(r) > 1)
+ {
+ fprintf(stderr, _("diff command failed with status %d: %s\n"), r, cmd);
+ exit(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(2);
+ }
+#endif
+
+ return WEXITSTATUS(r);
+}
+
+/*
+ * 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 *resultsfile, const char *default_expectfile)
+{
+ 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;
+
+ /*
+ * We can pass either the resultsfile or the expectfile, they should have
+ * the same type (filename.type) anyway.
+ */
+ platform_expectfile = get_expectfile(testname, resultsfile);
+
+ strlcpy(expectfile, default_expectfile, sizeof(expectfile));
+ if (platform_expectfile)
+ {
+ /*
+ * Replace everything after the last slash in expectfile with what the
+ * platform_expectfile contains.
+ */
+ char *p = strrchr(expectfile, '/');
+
+ if (p)
+ strcpy(++p, platform_expectfile);
+ }
+
+ /* Name to use for temporary diff file */
+ snprintf(diff, sizeof(diff), "%s.diff", resultsfile);
+
+ /* OK, run the diff */
+ snprintf(cmd, sizeof(cmd),
+ "diff %s \"%s\" \"%s\" > \"%s\"",
+ basic_diff_opts, expectfile, resultsfile, diff);
+
+ /* Is the diff file empty? */
+ if (run_diff(cmd, diff) == 0)
+ {
+ 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++)
+ {
+ char *alt_expectfile;
+
+ alt_expectfile = get_alternative_expectfile(expectfile, i);
+ if (!alt_expectfile)
+ {
+ fprintf(stderr, _("Unable to check secondary comparison files: %s\n"),
+ strerror(errno));
+ exit(2);
+ }
+
+ if (!file_exists(alt_expectfile))
+ {
+ free(alt_expectfile);
+ continue;
+ }
+
+ snprintf(cmd, sizeof(cmd),
+ "diff %s \"%s\" \"%s\" > \"%s\"",
+ basic_diff_opts, alt_expectfile, resultsfile, diff);
+
+ if (run_diff(cmd, diff) == 0)
+ {
+ unlink(diff);
+ free(alt_expectfile);
+ 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;
+ strlcpy(best_expect_file, alt_expectfile, sizeof(best_expect_file));
+ }
+ 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),
+ "diff %s \"%s\" \"%s\" > \"%s\"",
+ basic_diff_opts, default_expectfile, resultsfile, diff);
+
+ if (run_diff(cmd, 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;
+ strlcpy(best_expect_file, default_expectfile, sizeof(best_expect_file));
+ }
+ }
+
+ /*
+ * Use the best comparison file to generate the "pretty" diff, which we
+ * append to the diffs summary file.
+ */
+
+ /* Write diff header */
+ difffile = fopen(difffilename, "a");
+ if (difffile)
+ {
+ fprintf(difffile,
+ "diff %s %s %s\n",
+ pretty_diff_opts, best_expect_file, resultsfile);
+ fclose(difffile);
+ }
+
+ /* Run diff */
+ snprintf(cmd, sizeof(cmd),
+ "diff %s \"%s\" \"%s\" >> \"%s\"",
+ pretty_diff_opts, best_expect_file, resultsfile, difffilename);
+ run_diff(cmd, difffilename);
+
+ unlink(diff);
+ return true;
+}
+
+/*
+ * Wait for specified subprocesses to finish, and return their exit
+ * statuses into statuses[] and stop times into stoptimes[]
+ *
+ * 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, int *statuses, instr_time *stoptimes,
+ char **names, int num_tests)
+{
+ int tests_left;
+ int i;
+
+#ifdef WIN32
+ PID_TYPE *active_pids = pg_malloc(num_tests * sizeof(PID_TYPE));
+
+ memcpy(active_pids, pids, num_tests * sizeof(PID_TYPE));
+#endif
+
+ tests_left = num_tests;
+ while (tests_left > 0)
+ {
+ PID_TYPE p;
+
+#ifndef WIN32
+ int exit_status;
+
+ p = wait(&exit_status);
+
+ if (p == INVALID_PID)
+ {
+ fprintf(stderr, _("failed to wait for subprocesses: %s\n"),
+ strerror(errno));
+ exit(2);
+ }
+#else
+ DWORD exit_status;
+ int r;
+
+ r = WaitForMultipleObjects(tests_left, active_pids, FALSE, INFINITE);
+ if (r < WAIT_OBJECT_0 || r >= WAIT_OBJECT_0 + tests_left)
+ {
+ fprintf(stderr, _("failed to wait for subprocesses: error code %lu\n"),
+ GetLastError());
+ exit(2);
+ }
+ 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 */
+
+ 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;
+ INSTR_TIME_SET_CURRENT(stoptimes[i]);
+ if (names)
+ status(" %s", names[i]);
+ tests_left--;
+ break;
+ }
+ }
+ }
+
+#ifdef WIN32
+ free(active_pids);
+#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));
+#else
+ status(_(" (test process was terminated by signal %d: %s)"),
+ WTERMSIG(exitstatus), pg_strsignal(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, test_start_function startfunc,
+ postprocess_result_function postfunc)
+{
+#define MAX_PARALLEL_TESTS 100
+ 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];
+ instr_time starttimes[MAX_PARALLEL_TESTS];
+ instr_time stoptimes[MAX_PARALLEL_TESTS];
+ int statuses[MAX_PARALLEL_TESTS];
+ _stringlist *ignorelist = NULL;
+ char scbuf[1024];
+ FILE *scf;
+ int line_num = 0;
+
+ memset(tests, 0, sizeof(tests));
+ memset(resultfiles, 0, sizeof(resultfiles));
+ memset(expectfiles, 0, sizeof(expectfiles));
+ memset(tags, 0, sizeof(tags));
+
+ scf = fopen(schedule, "r");
+ if (!scf)
+ {
+ fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
+ progname, schedule, strerror(errno));
+ exit(2);
+ }
+
+ while (fgets(scbuf, sizeof(scbuf), 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(2);
+ }
+
+ num_tests = 0;
+ inword = false;
+ for (c = test;; c++)
+ {
+ if (*c == '\0' || isspace((unsigned char) *c))
+ {
+ if (inword)
+ {
+ /* Reached end of a test name */
+ char sav;
+
+ if (num_tests >= MAX_PARALLEL_TESTS)
+ {
+ fprintf(stderr, _("too many parallel tests (more than %d) in schedule file \"%s\" line %d: %s\n"),
+ MAX_PARALLEL_TESTS, schedule, line_num, scbuf);
+ exit(2);
+ }
+ sav = *c;
+ *c = '\0';
+ tests[num_tests] = pg_strdup(test);
+ num_tests++;
+ *c = sav;
+ inword = false;
+ }
+ if (*c == '\0')
+ break; /* loop exit is here */
+ }
+ else if (!inword)
+ {
+ /* Start of a test name */
+ test = c;
+ inword = true;
+ }
+ }
+
+ if (num_tests == 0)
+ {
+ fprintf(stderr, _("syntax error in schedule file \"%s\" line %d: %s\n"),
+ schedule, line_num, scbuf);
+ exit(2);
+ }
+
+ if (num_tests == 1)
+ {
+ status(_("test %-28s ... "), tests[0]);
+ pids[0] = (startfunc) (tests[0], &resultfiles[0], &expectfiles[0], &tags[0]);
+ INSTR_TIME_SET_CURRENT(starttimes[0]);
+ wait_for_tests(pids, statuses, stoptimes, NULL, 1);
+ /* status line is finished below */
+ }
+ else if (max_concurrent_tests > 0 && max_concurrent_tests < num_tests)
+ {
+ fprintf(stderr, _("too many parallel tests (more than %d) in schedule file \"%s\" line %d: %s\n"),
+ max_concurrent_tests, schedule, line_num, scbuf);
+ exit(2);
+ }
+ 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, statuses + oldest,
+ stoptimes + oldest,
+ tests + oldest, i - oldest);
+ oldest = i;
+ }
+ pids[i] = (startfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
+ INSTR_TIME_SET_CURRENT(starttimes[i]);
+ }
+ wait_for_tests(pids + oldest, statuses + oldest,
+ stoptimes + oldest,
+ tests + oldest, i - oldest);
+ status_end();
+ }
+ else
+ {
+ status(_("parallel group (%d tests): "), num_tests);
+ for (i = 0; i < num_tests; i++)
+ {
+ pids[i] = (startfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
+ INSTR_TIME_SET_CURRENT(starttimes[i]);
+ }
+ wait_for_tests(pids, statuses, stoptimes, 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(_(" %-28s ... "), 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,
+ tl = tl ? tl->next : NULL)
+ {
+ bool newdiff;
+
+ if (postfunc)
+ (*postfunc) (rl->str);
+ 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)
+ {
+ 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 ")); /* align with FAILED */
+ success_count++;
+ }
+
+ if (statuses[i] != 0)
+ log_child_failure(statuses[i]);
+
+ INSTR_TIME_SUBTRACT(stoptimes[i], starttimes[i]);
+ status(_(" %8.0f ms"), INSTR_TIME_GET_MILLISEC(stoptimes[i]));
+
+ status_end();
+ }
+
+ for (i = 0; i < num_tests; i++)
+ {
+ pg_free(tests[i]);
+ tests[i] = NULL;
+ free_stringlist(&resultfiles[i]);
+ free_stringlist(&expectfiles[i]);
+ free_stringlist(&tags[i]);
+ }
+ }
+
+ free_stringlist(&ignorelist);
+
+ fclose(scf);
+}
+
+/*
+ * Run a single test
+ */
+static void
+run_single_test(const char *test, test_start_function startfunc,
+ postprocess_result_function postfunc)
+{
+ PID_TYPE pid;
+ instr_time starttime;
+ instr_time stoptime;
+ int exit_status;
+ _stringlist *resultfiles = NULL;
+ _stringlist *expectfiles = NULL;
+ _stringlist *tags = NULL;
+ _stringlist *rl,
+ *el,
+ *tl;
+ bool differ = false;
+
+ status(_("test %-28s ... "), test);
+ pid = (startfunc) (test, &resultfiles, &expectfiles, &tags);
+ INSTR_TIME_SET_CURRENT(starttime);
+ wait_for_tests(&pid, &exit_status, &stoptime, 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,
+ tl = tl ? tl->next : NULL)
+ {
+ bool newdiff;
+
+ if (postfunc)
+ (*postfunc) (rl->str);
+ newdiff = results_differ(test, rl->str, el->str);
+ if (newdiff && tl)
+ {
+ printf("%s ", tl->str);
+ }
+ differ |= newdiff;
+ }
+
+ if (differ)
+ {
+ status(_("FAILED"));
+ fail_count++;
+ }
+ else
+ {
+ status(_("ok ")); /* align with FAILED */
+ success_count++;
+ }
+
+ if (exit_status != 0)
+ log_child_failure(exit_status);
+
+ INSTR_TIME_SUBTRACT(stoptime, starttime);
+ status(_(" %8.0f ms"), INSTR_TIME_GET_MILLISEC(stoptime));
+
+ 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 outputdir directory if not present */
+ if (!directory_exists(outputdir))
+ make_directory(outputdir);
+
+ /* create the log file (copy of running status output) */
+ snprintf(file, sizeof(file), "%s/regression.out", outputdir);
+ logfilename = pg_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(2);
+ }
+
+ /* create the diffs file as empty */
+ snprintf(file, sizeof(file), "%s/regression.diffs", outputdir);
+ difffilename = pg_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(2);
+ }
+ /* we don't keep the diffs file open continuously */
+ fclose(difffile);
+
+ /* also create the results directory if not present */
+ snprintf(file, sizeof(file), "%s/results", outputdir);
+ if (!directory_exists(file))
+ 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 bytea_output TO 'hex';"
+ "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';",
+ dbname, dbname, dbname, dbname, dbname, dbname);
+
+ /*
+ * Install any requested extensions. We use CREATE IF NOT EXISTS so that
+ * this will work whether or not the extension is preinstalled.
+ */
+ for (sl = loadextension; sl != NULL; sl = sl->next)
+ {
+ header(_("installing %s"), sl->str);
+ psql_command(dbname, "CREATE EXTENSION IF NOT EXISTS \"%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 void
+help(void)
+{
+ printf(_("PostgreSQL regression test driver\n"));
+ printf(_("\n"));
+ printf(_("Usage:\n %s [OPTION]... [EXTRA-TEST]...\n"), progname);
+ printf(_("\n"));
+ printf(_("Options:\n"));
+ printf(_(" --bindir=BINPATH use BINPATH for programs that are run;\n"));
+ printf(_(" if empty, use PATH from the environment\n"));
+ printf(_(" --config-auth=DATADIR update authentication settings for DATADIR\n"));
+ printf(_(" --create-role=ROLE create the specified role before testing\n"));
+ printf(_(" --dbname=DB use database DB (default \"regression\")\n"));
+ printf(_(" --debug turn on debug mode in programs that are run\n"));
+ printf(_(" --dlpath=DIR look for dynamic libraries in DIR\n"));
+ printf(_(" --encoding=ENCODING use ENCODING as the encoding\n"));
+ printf(_(" -h, --help show this help, then exit\n"));
+ printf(_(" --inputdir=DIR take input files from DIR (default \".\")\n"));
+ printf(_(" --launcher=CMD use CMD as launcher of psql\n"));
+ printf(_(" --load-extension=EXT load the named extension before running the\n"));
+ printf(_(" tests; can appear multiple times\n"));
+ printf(_(" --make-testtablespace-dir create testtablespace directory\n"));
+ printf(_(" --max-connections=N maximum number of concurrent connections\n"));
+ printf(_(" (default is 0, meaning unlimited)\n"));
+ printf(_(" --max-concurrent-tests=N maximum number of concurrent tests in schedule\n"));
+ printf(_(" (default is 0, meaning unlimited)\n"));
+ printf(_(" --outputdir=DIR place output files in DIR (default \".\")\n"));
+ printf(_(" --schedule=FILE use test ordering schedule from FILE\n"));
+ printf(_(" (can be used multiple times to concatenate)\n"));
+ printf(_(" --temp-instance=DIR create a temporary instance in DIR\n"));
+ printf(_(" --use-existing use an existing installation\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_("\n"));
+ printf(_("Options for \"temp-instance\" mode:\n"));
+ printf(_(" --no-locale use C locale\n"));
+ printf(_(" --port=PORT start postmaster on PORT\n"));
+ printf(_(" --temp-config=FILE append contents of FILE to temporary config\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 <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
+
+int
+regression_main(int argc, char *argv[],
+ init_function ifunc,
+ test_start_function startfunc,
+ postprocess_result_function postfunc)
+{
+ 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},
+ {"max-connections", required_argument, NULL, 5},
+ {"encoding", required_argument, NULL, 6},
+ {"outputdir", required_argument, NULL, 7},
+ {"schedule", required_argument, NULL, 8},
+ {"temp-instance", required_argument, NULL, 9},
+ {"no-locale", no_argument, NULL, 10},
+ {"host", required_argument, NULL, 13},
+ {"port", required_argument, NULL, 14},
+ {"user", required_argument, NULL, 15},
+ {"bindir", 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},
+ {"launcher", required_argument, NULL, 21},
+ {"load-extension", required_argument, NULL, 22},
+ {"config-auth", required_argument, NULL, 24},
+ {"max-concurrent-tests", required_argument, NULL, 25},
+ {"make-testtablespace-dir", no_argument, NULL, 26},
+ {NULL, 0, NULL, 0}
+ };
+
+ bool use_unix_sockets;
+ bool make_testtablespace_dir = false;
+ _stringlist *sl;
+ int c;
+ int i;
+ int option_index;
+ char buf[MAXPGPATH * 4];
+ char buf2[MAXPGPATH * 4];
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_regress"));
+
+ get_restricted_token();
+
+ atexit(stop_postmaster);
+
+#if !defined(HAVE_UNIX_SOCKETS)
+ use_unix_sockets = false;
+#elif defined(WIN32)
+
+ /*
+ * We don't use Unix-domain sockets on Windows by default, even if the
+ * build supports them. (See comment at remove_temp() for a reason.)
+ * Override at your own risk.
+ */
+ use_unix_sockets = getenv("PG_TEST_USE_UNIX_SOCKETS") ? true : false;
+#else
+ use_unix_sockets = true;
+#endif
+
+ if (!use_unix_sockets)
+ hostname = "localhost";
+
+ /*
+ * We call the initialization function here because that way we can set
+ * default parameters and let them be overwritten by the commandline.
+ */
+ ifunc(argc, argv);
+
+ if (getenv("PG_REGRESS_DIFF_OPTS"))
+ pretty_diff_opts = getenv("PG_REGRESS_DIFF_OPTS");
+
+ while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1)
+ {
+ switch (c)
+ {
+ case 'h':
+ help();
+ exit(0);
+ case 'V':
+ puts("pg_regress (PostgreSQL) " PG_VERSION);
+ exit(0);
+ case 1:
+
+ /*
+ * If a default database was specified, we need to remove it
+ * before we add the specified one.
+ */
+ free_stringlist(&dblist);
+ split_to_stringlist(optarg, ",", &dblist);
+ break;
+ case 2:
+ debug = true;
+ break;
+ case 3:
+ inputdir = pg_strdup(optarg);
+ break;
+ case 5:
+ max_connections = atoi(optarg);
+ break;
+ case 6:
+ encoding = pg_strdup(optarg);
+ break;
+ case 7:
+ outputdir = pg_strdup(optarg);
+ break;
+ case 8:
+ add_stringlist_item(&schedulelist, optarg);
+ break;
+ case 9:
+ temp_instance = make_absolute_path(optarg);
+ break;
+ case 10:
+ nolocale = true;
+ break;
+ case 13:
+ hostname = pg_strdup(optarg);
+ break;
+ case 14:
+ port = atoi(optarg);
+ port_specified_by_user = true;
+ break;
+ case 15:
+ user = pg_strdup(optarg);
+ break;
+ case 16:
+ /* "--bindir=" means to use PATH */
+ if (strlen(optarg))
+ bindir = pg_strdup(optarg);
+ else
+ bindir = NULL;
+ break;
+ case 17:
+ dlpath = pg_strdup(optarg);
+ break;
+ case 18:
+ split_to_stringlist(optarg, ",", &extraroles);
+ break;
+ case 19:
+ add_stringlist_item(&temp_configs, optarg);
+ break;
+ case 20:
+ use_existing = true;
+ break;
+ case 21:
+ launcher = pg_strdup(optarg);
+ break;
+ case 22:
+ add_stringlist_item(&loadextension, optarg);
+ break;
+ case 24:
+ config_auth_datadir = pg_strdup(optarg);
+ break;
+ case 25:
+ max_concurrent_tests = atoi(optarg);
+ break;
+ case 26:
+ make_testtablespace_dir = true;
+ break;
+ default:
+ /* getopt_long already emitted a complaint */
+ fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
+ progname);
+ exit(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 (config_auth_datadir)
+ {
+#ifdef ENABLE_SSPI
+ if (!use_unix_sockets)
+ config_sspi_auth(config_auth_datadir, user);
+#endif
+ exit(0);
+ }
+
+ if (temp_instance && !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. This aids !HAVE_UNIX_SOCKETS
+ * systems; elsewhere, the use of a private socket directory already
+ * prevents interference.
+ */
+ port = 0xC000 | (PG_VERSION_NUM & 0x3FFF);
+
+ inputdir = make_absolute_path(inputdir);
+ outputdir = make_absolute_path(outputdir);
+ dlpath = make_absolute_path(dlpath);
+
+ /*
+ * Initialization
+ */
+ open_result_files();
+
+ initialize_environment();
+
+#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
+ unlimit_core_size();
+#endif
+
+ if (make_testtablespace_dir)
+ prepare_testtablespace_dir();
+
+ if (temp_instance)
+ {
+ FILE *pg_conf;
+ const char *env_wait;
+ int wait_seconds;
+
+ /*
+ * Prepare the temp instance
+ */
+
+ if (directory_exists(temp_instance))
+ {
+ header(_("removing existing temp instance"));
+ if (!rmtree(temp_instance, true))
+ {
+ fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n"),
+ progname, temp_instance);
+ exit(2);
+ }
+ }
+
+ header(_("creating temporary instance"));
+
+ /* make the temp instance top directory */
+ make_directory(temp_instance);
+
+ /* and a directory for log files */
+ snprintf(buf, sizeof(buf), "%s/log", outputdir);
+ if (!directory_exists(buf))
+ make_directory(buf);
+
+ /* initdb */
+ header(_("initializing database system"));
+ snprintf(buf, sizeof(buf),
+ "\"%s%sinitdb\" -D \"%s/data\" --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ temp_instance,
+ debug ? " --debug" : "",
+ nolocale ? " --no-locale" : "",
+ outputdir);
+ if (system(buf))
+ {
+ fprintf(stderr, _("\n%s: initdb failed\nExamine %s/log/initdb.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
+ exit(2);
+ }
+
+ /*
+ * Adjust the default postgresql.conf for regression testing. The user
+ * can specify a file to be appended; in any case we expand logging
+ * and 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_instance);
+ 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(2);
+ }
+ fputs("\n# Configuration added by pg_regress\n\n", pg_conf);
+ fputs("log_autovacuum_min_duration = 0\n", pg_conf);
+ fputs("log_checkpoints = on\n", pg_conf);
+ fputs("log_line_prefix = '%m %b[%p] %q%a '\n", pg_conf);
+ fputs("log_lock_waits = on\n", pg_conf);
+ fputs("log_temp_files = 128kB\n", pg_conf);
+ fputs("max_prepared_transactions = 2\n", pg_conf);
+
+ for (sl = temp_configs; sl != NULL; sl = sl->next)
+ {
+ char *temp_config = sl->str;
+ 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(2);
+ }
+ while (fgets(line_buf, sizeof(line_buf), extra_conf) != NULL)
+ fputs(line_buf, pg_conf);
+ fclose(extra_conf);
+ }
+
+ fclose(pg_conf);
+
+#ifdef ENABLE_SSPI
+ if (!use_unix_sockets)
+ {
+ /*
+ * Since we successfully used the same buffer for the much-longer
+ * "initdb" command, this can't truncate.
+ */
+ snprintf(buf, sizeof(buf), "%s/data", temp_instance);
+ config_sspi_auth(buf, NULL);
+ }
+#elif !defined(HAVE_UNIX_SOCKETS)
+#error Platform has no means to secure the test installation.
+#endif
+
+ /*
+ * Check if there is a postmaster running already.
+ */
+ snprintf(buf2, sizeof(buf2),
+ "\"%s%spsql\" -X postgres <%s 2>%s",
+ bindir ? bindir : "",
+ 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(2);
+ }
+
+ fprintf(stderr, _("port %d apparently in use, trying %d\n"), port, port + 1);
+ port++;
+ sprintf(s, "%d", port);
+ setenv("PGPORT", s, 1);
+ }
+ else
+ break;
+ }
+
+ /*
+ * Start the temp postmaster
+ */
+ header(_("starting postmaster"));
+ snprintf(buf, sizeof(buf),
+ "\"%s%spostgres\" -D \"%s/data\" -F%s "
+ "-c \"listen_addresses=%s\" -k \"%s\" "
+ "> \"%s/log/postmaster.log\" 2>&1",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ temp_instance, debug ? " -d 5" : "",
+ hostname ? hostname : "", sockdir ? sockdir : "",
+ 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(2);
+ }
+
+ /*
+ * Wait till postmaster is able to accept connections; normally this
+ * is only a second or so, but Cygwin is reportedly *much* slower, and
+ * test builds using Valgrind or similar tools might be too. Hence,
+ * allow the default timeout of 60 seconds to be overridden from the
+ * PGCTLTIMEOUT environment variable.
+ */
+ env_wait = getenv("PGCTLTIMEOUT");
+ if (env_wait != NULL)
+ {
+ wait_seconds = atoi(env_wait);
+ if (wait_seconds <= 0)
+ wait_seconds = 60;
+ }
+ else
+ wait_seconds = 60;
+
+ for (i = 0; i < wait_seconds; i++)
+ {
+ /* Done if psql succeeds */
+ if (system(buf2) == 0)
+ break;
+
+ /*
+ * Fail immediately if postmaster has exited
+ */
+#ifndef WIN32
+ if (waitpid(postmaster_pid, NULL, WNOHANG) == postmaster_pid)
+#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(2);
+ }
+
+ pg_usleep(1000000L);
+ }
+ if (i >= wait_seconds)
+ {
+ fprintf(stderr, _("\n%s: postmaster did not respond within %d seconds\nExamine %s/log/postmaster.log for the reason\n"),
+ progname, wait_seconds, 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: error code %lu\n"),
+ progname, GetLastError());
+#endif
+
+ exit(2);
+ }
+
+ postmaster_running = true;
+
+#ifdef _WIN64
+/* need a series of two casts to convert HANDLE without compiler warning */
+#define ULONGPID(x) (unsigned long) (unsigned long long) (x)
+#else
+#define ULONGPID(x) (unsigned long) (x)
+#endif
+ printf(_("running on port %d with PID %lu\n"),
+ port, ULONGPID(postmaster_pid));
+ }
+ else
+ {
+ /*
+ * Using an existing installation, so may need to get rid of
+ * pre-existing database(s) and role(s)
+ */
+ 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(s) and role(s)
+ */
+ if (!use_existing)
+ {
+ for (sl = dblist; sl; sl = sl->next)
+ create_database(sl->str);
+ for (sl = extraroles; sl; sl = sl->next)
+ create_role(sl->str, dblist);
+ }
+
+ /*
+ * Ready to run the tests
+ */
+ header(_("running regression test queries"));
+
+ for (sl = schedulelist; sl != NULL; sl = sl->next)
+ {
+ run_schedule(sl->str, startfunc, postfunc);
+ }
+
+ for (sl = extra_tests; sl != NULL; sl = sl->next)
+ {
+ run_single_test(sl->str, startfunc, postfunc);
+ }
+
+ /*
+ * Shut down temp installation's postmaster
+ */
+ if (temp_instance)
+ {
+ header(_("shutting down postmaster"));
+ stop_postmaster();
+ }
+
+ /*
+ * If there were no errors, remove the temp instance immediately to
+ * conserve disk space. (If there were errors, we leave the instance in
+ * place for possible manual investigation.)
+ */
+ if (temp_instance && fail_count == 0 && fail_ignore_count == 0)
+ {
+ header(_("removing temporary instance"));
+ if (!rmtree(temp_instance, true))
+ fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n"),
+ progname, temp_instance);
+ }
+
+ 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(1);
+
+ return 0;
+}