postgresql/src/test/regress/pg_regress.c

2562 lines
63 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* 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.
*
2017-01-03 19:48:53 +01:00
* Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
2010-09-20 22:08:53 +02:00
* 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 "pg_regress.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"
/* for resultmap we need a list of pairs of strings */
typedef struct _resultmap
{
2006-10-04 02:30:14 +02:00
char *test;
char *type;
2006-10-04 02:30:14 +02:00
char *resultfile;
struct _resultmap *next;
} _resultmap;
/*
* Values obtained from Makefile.
*/
2007-11-15 22:14:46 +01:00
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 = "-C3";
#else
const char *basic_diff_opts = "-w";
const char *pretty_diff_opts = "-w -C3";
#endif
/* options settable from command line */
_stringlist *dblist = NULL;
2007-11-15 22:14:46 +01:00
bool debug = false;
char *inputdir = ".";
char *outputdir = ".";
char *bindir = PGBINDIR;
char *launcher = NULL;
static _stringlist *loadlanguage = NULL;
static _stringlist *loadextension = NULL;
2006-10-04 02:30:14 +02:00
static int max_connections = 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;
Allow read only connections during recovery, known as Hot Standby. Enabled by recovery_connections = on (default) and forcing archive recovery using a recovery.conf. Recovery processing now emulates the original transactions as they are replayed, providing full locking and MVCC behaviour for read only queries. Recovery must enter consistent state before connections are allowed, so there is a delay, typically short, before connections succeed. Replay of recovering transactions can conflict and in some cases deadlock with queries during recovery; these result in query cancellation after max_standby_delay seconds have expired. Infrastructure changes have minor effects on normal running, though introduce four new types of WAL record. New test mode "make standbycheck" allows regression tests of static command behaviour on a standby server while in recovery. Typical and extreme dynamic behaviours have been checked via code inspection and manual testing. Few port specific behaviours have been utilised, though primary testing has been on Linux only so far. This commit is the basic patch. Additional changes will follow in this release to enhance some aspects of behaviour, notably improved handling of conflicts, deadlock detection and query cancellation. Changes to VACUUM FULL are also required. Simon Riggs, with significant and lengthy review by Heikki Linnakangas, including streamlined redesign of snapshot creation and two-phase commit. Important contributions from Florian Pflug, Mark Kirkwood, Merlin Moncure, Greg Stark, Gianni Ciolli, Gabriele Bartolini, Hannu Krosing, Robert Haas, Tatsuo Ishii, Hiroyuki Yamada plus support and feedback from many other community members.
2009-12-19 02:32:45 +01:00
static bool use_existing = false;
static char *hostname = NULL;
2006-10-04 02:30:14 +02:00
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;
2006-10-04 02:30:14 +02:00
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)
2007-11-15 22:14:46 +01:00
static void
unlimit_core_size(void)
{
struct rlimit lim;
2007-11-15 22:14:46 +01:00
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;
2007-11-15 22:14:46 +01:00
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)
2006-10-04 02:30:14 +02:00
/* 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);
2007-11-15 22:14:46 +01:00
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 */
2006-10-04 02:30:14 +02:00
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
* Windows is not a HAVE_UNIX_SOCKETS platform.
*/
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 = pg_strdup("/tmp/pg_regress-XXXXXX");
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, char *replace, char *replacement)
{
2007-11-15 22:14:46 +01:00
char *ptr;
2007-11-15 22:14:46 +01:00
while ((ptr = strstr(string, replace)) != NULL)
{
char *dup = pg_strdup(string);
2007-02-07 01:52:35 +01:00
strlcpy(string, dup, ptr - string + 1);
strcat(string, replacement);
strcat(string, dup + (ptr - string) + strlen(replace));
free(dup);
}
}
/*
* Convert *.source found in the "source" directory, replacing certain tokens
* in the file contents with their intended values, and put the resulting files
* in the "dest" directory, replacing the ".source" prefix in their names with
* the given suffix.
*/
static void
convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, char *suffix)
{
2007-11-15 22:14:46 +01:00
char testtablespace[MAXPGPATH];
char indir[MAXPGPATH];
struct stat st;
int ret;
2007-11-15 22:14:46 +01:00
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++)
{
2007-11-15 22:14:46 +01:00
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"),
2007-11-15 22:14:46 +01:00
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);
}
2007-11-15 22:14:46 +01:00
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
2006-10-04 02:30:14 +02:00
* 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)
{
2006-10-04 02:30:14 +02:00
char buf[MAXPGPATH];
FILE *f;
/* scan the file ... */
snprintf(buf, sizeof(buf), "%s/resultmap", inputdir);
2006-10-04 02:30:14 +02:00
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))
{
2006-10-04 02:30:14 +02:00
char *platform;
char *file_type;
2006-10-04 02:30:14 +02:00
char *expected;
int i;
/* strip trailing whitespace, especially the newline */
i = strlen(buf);
2006-10-04 02:30:14 +02:00
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"),
2007-11-15 22:14:46 +01:00
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';
/*
2006-10-04 02:30:14 +02:00
* 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
2007-11-15 22:14:46 +01:00
const char *
get_expectfile(const char *testname, const char *file)
{
2007-11-15 22:14:46 +01:00
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)
{
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)
{
/*
2006-10-04 02:30:14 +02:00
* 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)
{
2006-10-04 02:30:14 +02:00
char s[16];
2006-10-04 02:30:14 +02:00
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)
{
2006-10-04 02:30:14 +02:00
char s[16];
2006-10-04 02:30:14 +02:00
sprintf(s, "%d", port);
doputenv("PGPORT", s);
}
if (user != NULL)
doputenv("PGUSER", user);
/*
* 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();
}
pg_attribute_unused()
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;
}
#ifdef ENABLE_SSPI
/*
* 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.
*/
static void
config_sspi_auth(const char *pgdata)
{
const char *accountname,
*domainname;
const char *username;
char *errstr;
bool have_ipv6;
char fname[MAXPGPATH];
int res;
FILE *hba,
*ident;
_stringlist *sl;
/*
* "username", the initdb-chosen bootstrap superuser name, may always
* match "accountname", the value SSPI authentication discovers. The
* underlying system functions do not clearly guarantee that.
*/
current_windows_user(&accountname, &domainname);
username = get_user_name(&errstr);
if (username == 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) - 1)
{
/*
* 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(username)) >= 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
/*
* Issue a command via psql, connecting to the specified database
*
* Since we use system(), this doesn't return until the operation finishes
*/
static void
2006-10-04 02:30:14 +02:00
psql_command(const char *database, const char *query,...)
{
2006-10-04 02:30:14 +02:00
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
2006-10-04 02:30:14 +02:00
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
*
2006-10-04 02:30:14 +02:00
* Instead of using system(), exec the shell directly, and tell it to
* "exec" the command too. This saves two useless processes per
2006-10-04 02:30:14 +02:00
* 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
2015-05-24 03:35:49 +02:00
PROCESS_INFORMATION pi;
char *cmdline2;
HANDLE restrictedToken;
memset(&pi, 0, sizeof(pi));
cmdline2 = psprintf("cmd /c \"%s\"", cmdline);
2015-05-24 03:35:49 +02:00
if ((restrictedToken =
CreateRestrictedProcess(cmdline2, &pi, progname)) == 0)
exit(2);
CloseHandle(pi.hThread);
return pi.hProcess;
#endif
}
/*
* Count bytes in file
*/
static long
file_size(const char *file)
{
2006-10-04 02:30:14 +02:00
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)
{
2006-10-04 02:30:14 +02:00
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)
{
2006-10-04 02:30:14 +02:00
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)
{
2007-11-15 22:14:46 +01:00
char *last_dot;
int ssize = strlen(expectfile) + 2 + 1;
char *tmp;
char *s;
2007-11-15 22:14:46 +01:00
if (!(tmp = (char *) malloc(ssize)))
return NULL;
2014-03-03 20:05:33 +01:00
if (!(s = (char *) malloc(ssize)))
{
free(tmp);
return NULL;
}
strcpy(tmp, expectfile);
2007-11-15 22:14:46 +01:00
last_dot = strrchr(tmp, '.');
if (!last_dot)
{
free(tmp);
free(s);
return NULL;
}
*last_dot = '\0';
2007-11-15 22:14:46 +01:00
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)
{
2006-10-04 02:30:14 +02:00
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
/*
2006-10-04 02:30:14 +02:00
* 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
2006-10-04 02:30:14 +02:00
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)
{
2006-10-04 02:30:14 +02:00
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;
2006-10-04 02:30:14 +02:00
/*
2007-11-15 22:14:46 +01:00
* 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));
2007-11-15 22:14:46 +01:00
if (platform_expectfile)
{
/*
* Replace everything after the last slash in expectfile with what the
* platform_expectfile contains.
*/
2007-11-15 22:14:46 +01:00
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++)
{
2007-11-15 22:14:46 +01:00
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);
}
2006-10-04 02:30:14 +02:00
/*
* 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));
}
}
/*
2006-10-04 02:30:14 +02:00
* 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);
run_diff(cmd, difffilename);
/* 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, and return their exit
* statuses into statuses[]
*
* 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, char **names, int num_tests)
{
2006-10-04 02:30:14 +02:00
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)
{
2006-10-04 02:30:14 +02:00
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;
2006-10-04 02:30:14 +02:00
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];
2006-10-04 02:30:14 +02:00
#endif /* WIN32 */
2006-10-04 02:30:14 +02:00
for (i = 0; i < num_tests; i++)
{
if (p == pids[i])
{
#ifdef WIN32
GetExitCodeProcess(pids[i], &exit_status);
CloseHandle(pids[i]);
#endif
pids[i] = INVALID_PID;
statuses[i] = (int) exit_status;
if (names)
status(" %s", names[i]);
tests_left--;
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));
#elif defined(HAVE_DECL_SYS_SIGLIST) && HAVE_DECL_SYS_SIGLIST
status(_(" (test process was terminated by signal %d: %s)"),
WTERMSIG(exitstatus),
WTERMSIG(exitstatus) < NSIG ?
sys_siglist[WTERMSIG(exitstatus)] : "(unknown))");
#else
status(_(" (test process was terminated by signal %d)"),
WTERMSIG(exitstatus));
#endif
}
else
status(_(" (test process exited with unrecognized status %d)"),
exitstatus);
}
/*
* Run all the tests specified in one schedule file
*/
static void
run_schedule(const char *schedule, test_function tfunc)
{
#define MAX_PARALLEL_TESTS 100
2006-10-04 02:30:14 +02:00
char *tests[MAX_PARALLEL_TESTS];
_stringlist *resultfiles[MAX_PARALLEL_TESTS];
_stringlist *expectfiles[MAX_PARALLEL_TESTS];
_stringlist *tags[MAX_PARALLEL_TESTS];
2006-10-04 02:30:14 +02:00
PID_TYPE pids[MAX_PARALLEL_TESTS];
int statuses[MAX_PARALLEL_TESTS];
_stringlist *ignorelist = NULL;
2006-10-04 02:30:14 +02:00
char scbuf[1024];
FILE *scf;
int line_num = 0;
2007-11-15 22:14:46 +01:00
memset(resultfiles, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
memset(expectfiles, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
memset(tags, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
scf = fopen(schedule, "r");
if (!scf)
{
fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
progname, schedule, strerror(errno));
exit(2);
}
while (fgets(scbuf, sizeof(scbuf), scf))
{
2006-10-04 02:30:14 +02:00
char *test = NULL;
char *c;
int num_tests;
bool inword;
int i;
line_num++;
for (i = 0; i < MAX_PARALLEL_TESTS; i++)
{
if (resultfiles[i] == NULL)
break;
free_stringlist(&resultfiles[i]);
free_stringlist(&expectfiles[i]);
free_stringlist(&tags[i]);
}
/* strip trailing whitespace, especially the newline */
i = strlen(scbuf);
2006-10-04 02:30:14 +02:00
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);
2006-10-04 02:30:14 +02:00
/*
* Note: ignore: lines do not run the test, they just say that
2006-10-04 02:30:14 +02:00
* 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; 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(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(2);
}
if (num_tests == 1)
{
Allow read only connections during recovery, known as Hot Standby. Enabled by recovery_connections = on (default) and forcing archive recovery using a recovery.conf. Recovery processing now emulates the original transactions as they are replayed, providing full locking and MVCC behaviour for read only queries. Recovery must enter consistent state before connections are allowed, so there is a delay, typically short, before connections succeed. Replay of recovering transactions can conflict and in some cases deadlock with queries during recovery; these result in query cancellation after max_standby_delay seconds have expired. Infrastructure changes have minor effects on normal running, though introduce four new types of WAL record. New test mode "make standbycheck" allows regression tests of static command behaviour on a standby server while in recovery. Typical and extreme dynamic behaviours have been checked via code inspection and manual testing. Few port specific behaviours have been utilised, though primary testing has been on Linux only so far. This commit is the basic patch. Additional changes will follow in this release to enhance some aspects of behaviour, notably improved handling of conflicts, deadlock detection and query cancellation. Changes to VACUUM FULL are also required. Simon Riggs, with significant and lengthy review by Heikki Linnakangas, including streamlined redesign of snapshot creation and two-phase commit. Important contributions from Florian Pflug, Mark Kirkwood, Merlin Moncure, Greg Stark, Gianni Ciolli, Gabriele Bartolini, Hannu Krosing, Robert Haas, Tatsuo Ishii, Hiroyuki Yamada plus support and feedback from many other community members.
2009-12-19 02:32:45 +01:00
status(_("test %-24s ... "), tests[0]);
2007-11-15 22:14:46 +01:00
pids[0] = (tfunc) (tests[0], &resultfiles[0], &expectfiles[0], &tags[0]);
wait_for_tests(pids, statuses, NULL, 1);
/* status line is finished below */
}
else if (max_connections > 0 && max_connections < num_tests)
{
2006-10-04 02:30:14 +02:00
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,
tests + oldest, i - oldest);
oldest = i;
}
2007-11-15 22:14:46 +01:00
pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
}
wait_for_tests(pids + oldest, statuses + oldest,
tests + oldest, i - oldest);
status_end();
}
else
{
status(_("parallel group (%d tests): "), num_tests);
for (i = 0; i < num_tests; i++)
{
2007-11-15 22:14:46 +01:00
pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]);
}
wait_for_tests(pids, statuses, tests, num_tests);
status_end();
}
/* Check results for all tests */
for (i = 0; i < num_tests; i++)
{
2007-11-15 22:14:46 +01:00
_stringlist *rl,
*el,
*tl;
bool differ = false;
if (num_tests > 1)
Allow read only connections during recovery, known as Hot Standby. Enabled by recovery_connections = on (default) and forcing archive recovery using a recovery.conf. Recovery processing now emulates the original transactions as they are replayed, providing full locking and MVCC behaviour for read only queries. Recovery must enter consistent state before connections are allowed, so there is a delay, typically short, before connections succeed. Replay of recovering transactions can conflict and in some cases deadlock with queries during recovery; these result in query cancellation after max_standby_delay seconds have expired. Infrastructure changes have minor effects on normal running, though introduce four new types of WAL record. New test mode "make standbycheck" allows regression tests of static command behaviour on a standby server while in recovery. Typical and extreme dynamic behaviours have been checked via code inspection and manual testing. Few port specific behaviours have been utilised, though primary testing has been on Linux only so far. This commit is the basic patch. Additional changes will follow in this release to enhance some aspects of behaviour, notably improved handling of conflicts, deadlock detection and query cancellation. Changes to VACUUM FULL are also required. Simon Riggs, with significant and lengthy review by Heikki Linnakangas, including streamlined redesign of snapshot creation and two-phase commit. Important contributions from Florian Pflug, Mark Kirkwood, Merlin Moncure, Greg Stark, Gianni Ciolli, Gabriele Bartolini, Hannu Krosing, Robert Haas, Tatsuo Ishii, Hiroyuki Yamada plus support and feedback from many other community members.
2009-12-19 02:32:45 +01:00
status(_(" %-24s ... "), tests[i]);
/*
* Advance over all three lists simultaneously.
*
2007-11-15 22:14:46 +01:00
* 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];
2007-11-15 22:14:46 +01:00
rl != NULL; /* rl and el have the same length */
rl = rl->next, el = el->next)
{
2007-11-15 22:14:46 +01:00
bool newdiff;
if (tl)
tl = tl->next; /* tl has the same length as rl and el
2007-11-15 22:14:46 +01:00
* if it exists */
newdiff = results_differ(tests[i], rl->str, el->str);
2007-11-15 22:14:46 +01:00
if (newdiff && tl)
{
printf("%s ", tl->str);
}
differ |= newdiff;
}
if (differ)
{
2006-10-04 02:30:14 +02:00
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++;
}
if (statuses[i] != 0)
log_child_failure(statuses[i]);
status_end();
}
}
free_stringlist(&ignorelist);
fclose(scf);
}
/*
* Run a single test
*/
static void
run_single_test(const char *test, test_function tfunc)
{
2006-10-04 02:30:14 +02:00
PID_TYPE pid;
int exit_status;
2007-06-12 15:26:45 +02:00
_stringlist *resultfiles = NULL;
_stringlist *expectfiles = NULL;
_stringlist *tags = NULL;
2007-11-15 22:14:46 +01:00
_stringlist *rl,
*el,
*tl;
bool differ = false;
status(_("test %-24s ... "), test);
2007-11-15 22:14:46 +01:00
pid = (tfunc) (test, &resultfiles, &expectfiles, &tags);
wait_for_tests(&pid, &exit_status, NULL, 1);
/*
* Advance over all three lists simultaneously.
*
2007-11-15 22:14:46 +01:00
* 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;
2007-11-15 22:14:46 +01:00
rl != NULL; /* rl and el have the same length */
rl = rl->next, el = el->next)
{
2007-11-15 22:14:46 +01:00
bool newdiff;
if (tl)
tl = tl->next; /* tl has the same length as rl and el if it
2007-11-15 22:14:46 +01:00
* exists */
newdiff = results_differ(test, rl->str, el->str);
2007-11-15 22:14:46 +01:00
if (newdiff && tl)
{
printf("%s ", tl->str);
}
differ |= newdiff;
}
if (differ)
{
status(_("FAILED"));
fail_count++;
}
else
{
status(_("ok"));
success_count++;
}
if (exit_status != 0)
log_child_failure(exit_status);
status_end();
}
/*
* Create the summary-output files (making them empty if already existing)
*/
static void
open_result_files(void)
{
2006-10-04 02:30:14 +02:00
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;
2007-11-15 22:14:46 +01:00
/*
* 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'" : "");
2007-11-15 22:14:46 +01:00
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';"
2007-11-15 22:14:46 +01:00
"ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';",
dbname, dbname, dbname, dbname, dbname, dbname);
/*
* Install any requested procedural languages. We use CREATE OR REPLACE
* so that this will work whether or not the language is preinstalled.
*/
for (sl = loadlanguage; sl != NULL; sl = sl->next)
{
header(_("installing %s"), sl->str);
psql_command(dbname, "CREATE OR REPLACE LANGUAGE \"%s\"", sl->str);
}
/*
2011-04-10 17:42:00 +02:00
* 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\"",
2007-11-15 22:14:46 +01:00
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(_(" --config-auth=DATADIR update authentication settings for DATADIR\n"));
printf(_(" --create-role=ROLE create the specified role before testing\n"));
printf(_(" --dbname=DB use database DB (default \"regression\")\n"));
printf(_(" --debug turn on debug mode in programs that are run\n"));
printf(_(" --dlpath=DIR look for dynamic libraries in DIR\n"));
printf(_(" --encoding=ENCODING use ENCODING as the encoding\n"));
printf(_(" --inputdir=DIR take input files from DIR (default \".\")\n"));
printf(_(" --launcher=CMD use CMD as launcher of psql\n"));
printf(_(" --load-extension=EXT load the named extension before running the\n"));
printf(_(" tests; can appear multiple times\n"));
printf(_(" --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(_(" --outputdir=DIR place output files in DIR (default \".\")\n"));
printf(_(" --schedule=FILE use test ordering schedule from FILE\n"));
printf(_(" (can be used multiple times to concatenate)\n"));
printf(_(" --temp-instance=DIR create a temporary instance in DIR\n"));
2015-05-24 03:35:49 +02:00
printf(_(" --use-existing use an existing installation\n"));
printf(_("\n"));
printf(_("Options for \"temp-instance\" mode:\n"));
printf(_(" --no-locale use C locale\n"));
printf(_(" --port=PORT start postmaster on PORT\n"));
printf(_(" --temp-config=FILE append contents of FILE to temporary config\n"));
printf(_("\n"));
printf(_("Options for using an existing installation:\n"));
printf(_(" --host=HOST use postmaster running on HOST\n"));
printf(_(" --port=PORT use postmaster running at PORT\n"));
printf(_(" --user=USER connect as USER\n"));
printf(_("\n"));
printf(_("The exit status is 0 if all tests passed, 1 if some tests failed, and 2\n"));
printf(_("if the tests could not be run for some reason.\n"));
printf(_("\n"));
printf(_("Report bugs to <pgsql-bugs@postgresql.org>.\n"));
}
int
regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc)
{
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},
{"encoding", required_argument, NULL, 6},
{"outputdir", required_argument, NULL, 7},
{"schedule", required_argument, NULL, 8},
{"temp-instance", required_argument, NULL, 9},
{"no-locale", no_argument, NULL, 10},
{"host", required_argument, NULL, 13},
{"port", required_argument, NULL, 14},
{"user", required_argument, NULL, 15},
{"bindir", required_argument, NULL, 16},
{"dlpath", required_argument, NULL, 17},
{"create-role", required_argument, NULL, 18},
{"temp-config", required_argument, NULL, 19},
Allow read only connections during recovery, known as Hot Standby. Enabled by recovery_connections = on (default) and forcing archive recovery using a recovery.conf. Recovery processing now emulates the original transactions as they are replayed, providing full locking and MVCC behaviour for read only queries. Recovery must enter consistent state before connections are allowed, so there is a delay, typically short, before connections succeed. Replay of recovering transactions can conflict and in some cases deadlock with queries during recovery; these result in query cancellation after max_standby_delay seconds have expired. Infrastructure changes have minor effects on normal running, though introduce four new types of WAL record. New test mode "make standbycheck" allows regression tests of static command behaviour on a standby server while in recovery. Typical and extreme dynamic behaviours have been checked via code inspection and manual testing. Few port specific behaviours have been utilised, though primary testing has been on Linux only so far. This commit is the basic patch. Additional changes will follow in this release to enhance some aspects of behaviour, notably improved handling of conflicts, deadlock detection and query cancellation. Changes to VACUUM FULL are also required. Simon Riggs, with significant and lengthy review by Heikki Linnakangas, including streamlined redesign of snapshot creation and two-phase commit. Important contributions from Florian Pflug, Mark Kirkwood, Merlin Moncure, Greg Stark, Gianni Ciolli, Gabriele Bartolini, Hannu Krosing, Robert Haas, Tatsuo Ishii, Hiroyuki Yamada plus support and feedback from many other community members.
2009-12-19 02:32:45 +01:00
{"use-existing", no_argument, NULL, 20},
{"launcher", required_argument, NULL, 21},
{"load-extension", required_argument, NULL, 22},
{"config-auth", required_argument, NULL, 24},
{NULL, 0, NULL, 0}
};
_stringlist *sl;
int c;
int i;
int option_index;
char buf[MAXPGPATH * 4];
char buf2[MAXPGPATH * 4];
progname = get_progname(argv[0]);
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_regress"));
atexit(stop_postmaster);
#ifndef HAVE_UNIX_SOCKETS
/* no unix domain sockets available, so change default */
hostname = "localhost";
#endif
/*
* We call the initialization function here because that way we can set
* default parameters and let them be overwritten by the commandline.
*/
ifunc(argc, argv);
if (getenv("PG_REGRESS_DIFF_OPTS"))
pretty_diff_opts = getenv("PG_REGRESS_DIFF_OPTS");
while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1)
{
switch (c)
{
case 'h':
help();
exit(0);
case 'V':
puts("pg_regress (PostgreSQL) " PG_VERSION);
exit(0);
case 1:
2007-11-15 22:14:46 +01:00
/*
* If a default database was specified, we need to remove it
* before we add the specified one.
*/
free_stringlist(&dblist);
split_to_stringlist(optarg, ",", &dblist);
break;
case 2:
debug = true;
break;
case 3:
inputdir = pg_strdup(optarg);
break;
case 4:
add_stringlist_item(&loadlanguage, optarg);
break;
case 5:
max_connections = atoi(optarg);
break;
case 6:
encoding = pg_strdup(optarg);
break;
case 7:
outputdir = pg_strdup(optarg);
break;
case 8:
add_stringlist_item(&schedulelist, optarg);
break;
case 9:
temp_instance = make_absolute_path(optarg);
break;
case 10:
nolocale = true;
break;
case 13:
hostname = pg_strdup(optarg);
break;
case 14:
port = atoi(optarg);
port_specified_by_user = true;
break;
case 15:
user = pg_strdup(optarg);
break;
case 16:
/* "--bindir=" means to use PATH */
if (strlen(optarg))
bindir = pg_strdup(optarg);
else
bindir = NULL;
break;
case 17:
dlpath = pg_strdup(optarg);
break;
case 18:
split_to_stringlist(optarg, ",", &extraroles);
break;
case 19:
add_stringlist_item(&temp_configs, optarg);
break;
Allow read only connections during recovery, known as Hot Standby. Enabled by recovery_connections = on (default) and forcing archive recovery using a recovery.conf. Recovery processing now emulates the original transactions as they are replayed, providing full locking and MVCC behaviour for read only queries. Recovery must enter consistent state before connections are allowed, so there is a delay, typically short, before connections succeed. Replay of recovering transactions can conflict and in some cases deadlock with queries during recovery; these result in query cancellation after max_standby_delay seconds have expired. Infrastructure changes have minor effects on normal running, though introduce four new types of WAL record. New test mode "make standbycheck" allows regression tests of static command behaviour on a standby server while in recovery. Typical and extreme dynamic behaviours have been checked via code inspection and manual testing. Few port specific behaviours have been utilised, though primary testing has been on Linux only so far. This commit is the basic patch. Additional changes will follow in this release to enhance some aspects of behaviour, notably improved handling of conflicts, deadlock detection and query cancellation. Changes to VACUUM FULL are also required. Simon Riggs, with significant and lengthy review by Heikki Linnakangas, including streamlined redesign of snapshot creation and two-phase commit. Important contributions from Florian Pflug, Mark Kirkwood, Merlin Moncure, Greg Stark, Gianni Ciolli, Gabriele Bartolini, Hannu Krosing, Robert Haas, Tatsuo Ishii, Hiroyuki Yamada plus support and feedback from many other community members.
2009-12-19 02:32:45 +01:00
case 20:
use_existing = true;
break;
case 21:
launcher = pg_strdup(optarg);
break;
case 22:
add_stringlist_item(&loadextension, optarg);
break;
case 24:
config_auth_datadir = pg_strdup(optarg);
break;
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
progname);
exit(2);
}
}
/*
* if we still have arguments, they are extra tests to run
*/
while (argc - optind >= 1)
{
add_stringlist_item(&extra_tests, argv[optind]);
optind++;
}
if (config_auth_datadir)
{
#ifdef ENABLE_SSPI
config_sspi_auth(config_auth_datadir);
#endif
exit(0);
}
if (temp_instance && !port_specified_by_user)
/*
* To reduce chances of interference with parallel installations, use
* a port number starting in the private range (49152-65535)
* calculated from the version number. This aids !HAVE_UNIX_SOCKETS
* systems; elsewhere, the use of a private socket directory already
* prevents interference.
*/
port = 0xC000 | (PG_VERSION_NUM & 0x3FFF);
inputdir = make_absolute_path(inputdir);
outputdir = make_absolute_path(outputdir);
dlpath = make_absolute_path(dlpath);
/*
* Initialization
*/
open_result_files();
initialize_environment();
#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
unlimit_core_size();
#endif
if (temp_instance)
{
FILE *pg_conf;
const char *env_wait;
int wait_seconds;
/*
* Prepare the temp instance
*/
if (directory_exists(temp_instance))
{
header(_("removing existing temp instance"));
if (!rmtree(temp_instance, true))
{
fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n"),
progname, temp_instance);
exit(2);
}
}
header(_("creating temporary instance"));
/* make the temp instance top directory */
make_directory(temp_instance);
/* and a directory for log files */
snprintf(buf, sizeof(buf), "%s/log", outputdir);
if (!directory_exists(buf))
make_directory(buf);
/* initdb */
header(_("initializing database system"));
snprintf(buf, sizeof(buf),
"\"%s%sinitdb\" -D \"%s/data\" --no-clean --no-sync%s%s > \"%s/log/initdb.log\" 2>&1",
bindir ? bindir : "",
bindir ? "/" : "",
temp_instance,
debug ? " --debug" : "",
nolocale ? " --no-locale" : "",
outputdir);
if (system(buf))
{
fprintf(stderr, _("\n%s: initdb failed\nExamine %s/log/initdb.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
exit(2);
}
/*
* Adjust the default postgresql.conf for regression testing. The user
* can specify a file to be appended; in any case we expand logging
* and set max_prepared_transactions to enable testing of prepared
* xacts. (Note: to reduce the probability of unexpected shmmax
* failures, don't set max_prepared_transactions any higher than
* actually needed by the prepared_xacts regression test.)
*/
snprintf(buf, sizeof(buf), "%s/data/postgresql.conf", temp_instance);
pg_conf = fopen(buf, "a");
if (pg_conf == NULL)
{
fprintf(stderr, _("\n%s: could not open \"%s\" for adding extra config: %s\n"), progname, buf, strerror(errno));
exit(2);
}
fputs("\n# Configuration added by pg_regress\n\n", pg_conf);
fputs("log_autovacuum_min_duration = 0\n", pg_conf);
fputs("log_checkpoints = on\n", pg_conf);
fputs("log_line_prefix = '%m [%p] %q%a '\n", pg_conf);
fputs("log_lock_waits = on\n", pg_conf);
fputs("log_temp_files = 128kB\n", pg_conf);
fputs("max_prepared_transactions = 2\n", pg_conf);
for (sl = temp_configs; sl != NULL; sl = sl->next)
{
char *temp_config = sl->str;
2007-11-15 22:14:46 +01:00
FILE *extra_conf;
char line_buf[1024];
2007-11-15 22:14:46 +01:00
extra_conf = fopen(temp_config, "r");
if (extra_conf == NULL)
{
fprintf(stderr, _("\n%s: could not open \"%s\" to read extra config: %s\n"), progname, temp_config, strerror(errno));
exit(2);
}
2007-11-15 22:14:46 +01:00
while (fgets(line_buf, sizeof(line_buf), extra_conf) != NULL)
fputs(line_buf, pg_conf);
fclose(extra_conf);
}
fclose(pg_conf);
#ifdef ENABLE_SSPI
/*
* Since we successfully used the same buffer for the much-longer
* "initdb" command, this can't truncate.
*/
snprintf(buf, sizeof(buf), "%s/data", temp_instance);
config_sspi_auth(buf);
#elif !defined(HAVE_UNIX_SOCKETS)
#error Platform has no means to secure the test installation.
#endif
/*
* Check if there is a postmaster running already.
*/
snprintf(buf2, sizeof(buf2),
"\"%s%spsql\" -X postgres <%s 2>%s",
bindir ? bindir : "",
bindir ? "/" : "",
DEVNULL, DEVNULL);
for (i = 0; i < 16; i++)
{
if (system(buf2) == 0)
{
char s[16];
if (port_specified_by_user || i == 15)
{
fprintf(stderr, _("port %d apparently in use\n"), port);
if (!port_specified_by_user)
fprintf(stderr, _("%s: could not determine an available port\n"), progname);
fprintf(stderr, _("Specify an unused port using the --port option or shut down any conflicting PostgreSQL servers.\n"));
exit(2);
}
fprintf(stderr, _("port %d apparently in use, trying %d\n"), port, port + 1);
port++;
sprintf(s, "%d", port);
doputenv("PGPORT", s);
}
else
break;
}
/*
* Start the temp postmaster
*/
header(_("starting postmaster"));
snprintf(buf, sizeof(buf),
"\"%s%spostgres\" -D \"%s/data\" -F%s "
"-c \"listen_addresses=%s\" -k \"%s\" "
"> \"%s/log/postmaster.log\" 2>&1",
bindir ? bindir : "",
bindir ? "/" : "",
temp_instance, debug ? " -d 5" : "",
hostname ? hostname : "", sockdir ? sockdir : "",
outputdir);
postmaster_pid = spawn_process(buf);
if (postmaster_pid == INVALID_PID)
{
fprintf(stderr, _("\n%s: could not spawn postmaster: %s\n"),
progname, strerror(errno));
exit(2);
}
/*
* Wait till postmaster is able to accept connections; normally this
* is only a second or so, but Cygwin is reportedly *much* slower, and
* test builds using Valgrind or similar tools might be too. Hence,
* allow the default timeout of 60 seconds to be overridden from the
* PGCTLTIMEOUT environment variable.
*/
env_wait = getenv("PGCTLTIMEOUT");
if (env_wait != NULL)
{
wait_seconds = atoi(env_wait);
if (wait_seconds <= 0)
wait_seconds = 60;
}
else
wait_seconds = 60;
for (i = 0; i < wait_seconds; i++)
{
/* Done if psql succeeds */
if (system(buf2) == 0)
break;
/*
* Fail immediately if postmaster has exited
*/
#ifndef WIN32
if (kill(postmaster_pid, 0) != 0)
#else
if (WaitForSingleObject(postmaster_pid, 0) == WAIT_OBJECT_0)
#endif
{
fprintf(stderr, _("\n%s: postmaster failed\nExamine %s/log/postmaster.log for the reason\n"), progname, outputdir);
exit(2);
}
pg_usleep(1000000L);
}
if (i >= wait_seconds)
{
fprintf(stderr, _("\n%s: postmaster did not respond within %d seconds\nExamine %s/log/postmaster.log for the reason\n"),
progname, wait_seconds, outputdir);
/*
2006-10-04 02:30:14 +02:00
* If we get here, the postmaster is probably wedged somewhere in
* startup. Try to kill it ungracefully rather than leaving a
* stuck postmaster that might interfere with subsequent test
* attempts.
*/
#ifndef WIN32
if (kill(postmaster_pid, SIGKILL) != 0 &&
errno != ESRCH)
fprintf(stderr, _("\n%s: could not kill failed postmaster: %s\n"),
progname, strerror(errno));
#else
if (TerminateProcess(postmaster_pid, 255) == 0)
fprintf(stderr, _("\n%s: could not kill failed postmaster: error code %lu\n"),
progname, GetLastError());
#endif
exit(2);
}
postmaster_running = true;
#ifdef _WIN64
/* need a series of two casts to convert HANDLE without compiler warning */
#define ULONGPID(x) (unsigned long) (unsigned long long) (x)
#else
#define ULONGPID(x) (unsigned long) (x)
#endif
2011-06-18 23:37:30 +02:00
printf(_("running on port %d with PID %lu\n"),
port, ULONGPID(postmaster_pid));
}
else
{
/*
* Using an existing installation, so may need to get rid of
* pre-existing database(s) and role(s)
*/
Allow read only connections during recovery, known as Hot Standby. Enabled by recovery_connections = on (default) and forcing archive recovery using a recovery.conf. Recovery processing now emulates the original transactions as they are replayed, providing full locking and MVCC behaviour for read only queries. Recovery must enter consistent state before connections are allowed, so there is a delay, typically short, before connections succeed. Replay of recovering transactions can conflict and in some cases deadlock with queries during recovery; these result in query cancellation after max_standby_delay seconds have expired. Infrastructure changes have minor effects on normal running, though introduce four new types of WAL record. New test mode "make standbycheck" allows regression tests of static command behaviour on a standby server while in recovery. Typical and extreme dynamic behaviours have been checked via code inspection and manual testing. Few port specific behaviours have been utilised, though primary testing has been on Linux only so far. This commit is the basic patch. Additional changes will follow in this release to enhance some aspects of behaviour, notably improved handling of conflicts, deadlock detection and query cancellation. Changes to VACUUM FULL are also required. Simon Riggs, with significant and lengthy review by Heikki Linnakangas, including streamlined redesign of snapshot creation and two-phase commit. Important contributions from Florian Pflug, Mark Kirkwood, Merlin Moncure, Greg Stark, Gianni Ciolli, Gabriele Bartolini, Hannu Krosing, Robert Haas, Tatsuo Ishii, Hiroyuki Yamada plus support and feedback from many other community members.
2009-12-19 02:32:45 +01:00
if (!use_existing)
{
for (sl = dblist; sl; sl = sl->next)
drop_database_if_exists(sl->str);
for (sl = extraroles; sl; sl = sl->next)
drop_role_if_exists(sl->str);
}
}
/*
* Create the test database(s) and role(s)
*/
Allow read only connections during recovery, known as Hot Standby. Enabled by recovery_connections = on (default) and forcing archive recovery using a recovery.conf. Recovery processing now emulates the original transactions as they are replayed, providing full locking and MVCC behaviour for read only queries. Recovery must enter consistent state before connections are allowed, so there is a delay, typically short, before connections succeed. Replay of recovering transactions can conflict and in some cases deadlock with queries during recovery; these result in query cancellation after max_standby_delay seconds have expired. Infrastructure changes have minor effects on normal running, though introduce four new types of WAL record. New test mode "make standbycheck" allows regression tests of static command behaviour on a standby server while in recovery. Typical and extreme dynamic behaviours have been checked via code inspection and manual testing. Few port specific behaviours have been utilised, though primary testing has been on Linux only so far. This commit is the basic patch. Additional changes will follow in this release to enhance some aspects of behaviour, notably improved handling of conflicts, deadlock detection and query cancellation. Changes to VACUUM FULL are also required. Simon Riggs, with significant and lengthy review by Heikki Linnakangas, including streamlined redesign of snapshot creation and two-phase commit. Important contributions from Florian Pflug, Mark Kirkwood, Merlin Moncure, Greg Stark, Gianni Ciolli, Gabriele Bartolini, Hannu Krosing, Robert Haas, Tatsuo Ishii, Hiroyuki Yamada plus support and feedback from many other community members.
2009-12-19 02:32:45 +01:00
if (!use_existing)
{
for (sl = dblist; sl; sl = sl->next)
create_database(sl->str);
for (sl = extraroles; sl; sl = sl->next)
create_role(sl->str, dblist);
}
/*
* Ready to run the tests
*/
header(_("running regression test queries"));
for (sl = schedulelist; sl != NULL; sl = sl->next)
{
run_schedule(sl->str, tfunc);
}
for (sl = extra_tests; sl != NULL; sl = sl->next)
{
run_single_test(sl->str, tfunc);
}
/*
* Shut down temp installation's postmaster
*/
if (temp_instance)
{
header(_("shutting down postmaster"));
stop_postmaster();
}
/*
* If there were no errors, remove the temp instance immediately to
2015-05-24 03:35:49 +02:00
* conserve disk space. (If there were errors, we leave the instance in
* place for possible manual investigation.)
*/
if (temp_instance && fail_count == 0 && fail_ignore_count == 0)
{
header(_("removing temporary instance"));
if (!rmtree(temp_instance, true))
fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n"),
progname, temp_instance);
}
fclose(logfile);
/*
* Emit nice-looking summary message
*/
if (fail_count == 0 && fail_ignore_count == 0)
snprintf(buf, sizeof(buf),
_(" All %d tests passed. "),
success_count);
2006-10-04 02:30:14 +02:00
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);
2006-10-04 02:30:14 +02:00
else if (fail_ignore_count == 0) /* fail_count>0 && fail_ignore_count=0 */
snprintf(buf, sizeof(buf),
_(" %d of %d tests failed. "),
fail_count,
2006-10-04 02:30:14 +02:00
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. "),
2006-10-04 02:30:14 +02:00
fail_count + fail_ignore_count,
success_count + fail_count + fail_ignore_count,
fail_ignore_count);
putchar('\n');
for (i = strlen(buf); i > 0; i--)
putchar('=');
printf("\n%s\n", buf);
for (i = strlen(buf); i > 0; i--)
putchar('=');
putchar('\n');
putchar('\n');
if (file_size(difffilename) > 0)
{
printf(_("The differences that caused some tests to fail can be viewed in the\n"
"file \"%s\". A copy of the test summary that you see\n"
"above is saved in the file \"%s\".\n\n"),
difffilename, logfilename);
}
else
{
unlink(difffilename);
unlink(logfilename);
}
if (fail_count != 0)
exit(1);
return 0;
}