/src/test/regress/pg_regress.c
C | 2635 lines | 1852 code | 287 blank | 496 comment | 347 complexity | 9c1dccb23dcbc80e54470457feac1b19 MD5 | raw file
Possible License(s): AGPL-3.0
Large files files are truncated, but you can click here to view the full file
- /*-------------------------------------------------------------------------
- *
- * 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-2020, 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/username.h"
- #include "getopt_long.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 a string in a string with a different string.
- * NOTE: Assumes there is enough room in the target buffer!
- */
- void
- replace_string(char *string, const char *replace, const char *replacement)
- {
- char *ptr;
- while ((ptr = strstr(string, replace)) != NULL)
- {
- char *dup = pg_strdup(string);
- strlcpy(string, dup, ptr - string + 1);
- strcat(string, replacement);
- strcat(string, dup + (ptr - string) + strlen(replace));
- free(dup);
- }
- }
- /*
- * Convert *.source found in the "source" directory, replacing certain tokens
- * in the file contents with their intended values, and put the resulting files
- * in the "dest" directory, replacing the ".source" prefix in their names with
- * the given suffix.
- */
- static void
- convert_sourcefiles_in(const char *source_subdir, const char *dest_dir, const char *dest_subdir, const char *suffix)
- {
- char testtablespace[MAXPGPATH];
- char indir[MAXPGPATH];
- struct stat st;
- int ret;
- char **name;
- char **names;
- int count = 0;
- snprintf(indir, MAXPGPATH, "%s/%s", inputdir, source_subdir);
- /* Check that indir actually exists and is a directory */
- ret = stat(indir, &st);
- if (ret != 0 || !S_ISDIR(st.st_mode))
- {
- /*
- * No warning, to avoid noise in tests that do not have these
- * directories; for example, ecpg, contrib and src/pl.
- */
- return;
- }
- names = pgfnames(indir);
- if (!names)
- /* Error logged in pgfnames */
- exit(2);
- snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir);
- #ifdef WIN32
- /*
- * On Windows only, clean out the test tablespace dir, or create it if it
- * doesn't exist. On other platforms we expect the Makefile to take care
- * of that. (We don't migrate that functionality in here because it'd be
- * harder to cope with platform-specific issues such as SELinux.)
- *
- * XXX it would be better if pg_regress.c had nothing at all to do with
- * testtablespace, and this were handled by a .BAT file or similar on
- * Windows. See pgsql-hackers discussion of 2008-01-18.
- */
- if (directory_exists(testtablespace))
- if (!rmtree(testtablespace, true))
- {
- fprintf(stderr, _("\n%s: could not remove test tablespace \"%s\"\n"),
- progname, testtablespace);
- exit(2);
- }
- make_directory(testtablespace);
- #endif
- /* finally loop on each file and do the replacement */
- for (name = names; *name; name++)
- {
- char srcfile[MAXPGPATH];
- char destfile[MAXPGPATH];
- char prefix[MAXPGPATH];
- FILE *infile,
- *outfile;
- char line[1024];
- /* reject filenames not finishing in ".source" */
- if (strlen(*name) < 8)
- continue;
- if (strcmp(*name + strlen(*name) - 7, ".source") != 0)
- continue;
- count++;
- /* build the full actual paths to open */
- snprintf(prefix, strlen(*name) - 6, "%s", *name);
- snprintf(srcfile, MAXPGPATH, "%s/%s", indir, *name);
- snprintf(destfile, MAXPGPATH, "%s/%s/%s.%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);
- }
- while (fgets(line, sizeof(line), infile))
- {
- replace_string(line, "@abs_srcdir@", inputdir);
- replace_string(line, "@abs_builddir@", outputdir);
- replace_string(line, "@testtablespace@", testtablespace);
- replace_string(line, "@libdir@", dlpath);
- replace_string(line, "@DLSUFFIX@", DLSUFFIX);
- fputs(line, outfile);
- }
- fclose(infile);
- fclose(outfile);
- }
- /*
- * If we didn't process any files, complain because it probably means
- * somebody neglected to pass the needed --inputdir argument.
- */
- if (count <= 0)
- {
- fprintf(stderr, _("%s: no *.source files found in \"%s\"\n"),
- progname, indir);
- exit(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");
- }
- /*
- * 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;
- }
- /*
- * Handy subroutine for setting an environment variable "var" to "val"
- */
- static void
- doputenv(const char *var, const char *val)
- {
- char *s;
- s = psprintf("%s=%s", var, val);
- putenv(s);
- }
- /*
- * Prepare environment variables for running regression tests
- */
- static void
- initialize_environment(void)
- {
- /*
- * Set default application_name. (The test_function may choose to
- * override this, but if it doesn't, we have something useful in place.)
- */
- putenv("PGAPPNAME=pg_regress");
- 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__)
- putenv("LANG=C");
- #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");
- putenv("LC_MESSAGES=C");
- /*
- * Set encoding 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");
- /*
- * 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("PGOPTIONS=%s %s",
- old_pgoptions, my_pgoptions);
- putenv(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.
- */
- unsetenv("PGDATABASE");
- unsetenv("PGUSER");
- unsetenv("PGSERVICE");
- unsetenv("PGSSLMODE");
- unsetenv("PGREQUIRESSL");
- unsetenv("PGCONNECT_TIMEOUT");
- unsetenv("PGDATA");
- #ifdef HAVE_UNIX_SOCKETS
- if (hostname != NULL)
- doputenv("PGHOST", hostname);
- else
- {
- sockdir = getenv("PG_REGRESS_SOCK_DIR");
- if (!sockdir)
- sockdir = make_temp_sockdir();
- doputenv("PGHOST", sockdir);
- }
- #else
- Assert(hostname != NULL);
- doputenv("PGHOST", hostname);
- #endif
- unsetenv("PGHOSTADDR");
- if (port != -1)
- {
- char s[16];
- sprintf(s, "%d", port);
- doputenv("PGPORT", s);
- }
- }
- 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);
- /*
- * 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");
- #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"));
- }
- 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_function tfunc)
- {
- #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] = (tfunc) (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] = (tfunc) (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] = (tfunc) (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;
- 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_function tfunc)
- {
- 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 = (tfunc) (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;
- 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 …
Large files files are truncated, but you can click here to view the full file