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