*
* This code is released under the terms of the PostgreSQL License.
*
- * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/test/regress/pg_regress.c
*-------------------------------------------------------------------------
*/
-#include "pg_regress.h"
+#include "postgres_fe.h"
#include <ctype.h>
#include <sys/stat.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
} _resultmap;
/*
- * Values obtained from pg_config_paths.h and Makefile. The PG installation
- * paths are only used in temp_install mode: we use these strings to find
- * out where "make install" will put stuff under the temp_install directory.
- * In non-temp_install mode, the only thing we need is the location of psql,
- * which we expect to find in psqldir, or in the PATH if psqldir isn't given.
- *
- * XXX Because pg_regress is not installed in bindir, we can't support
- * this for relocatable trees as it is. --psqldir would need to be
- * specified in those cases.
+ * Values obtained from Makefile.
*/
-char *bindir = PGBINDIR;
-char *libdir = LIBDIR;
-char *datadir = PGSHAREDIR;
char *host_platform = HOST_TUPLE;
-#ifndef WIN32_ONLY_COMPILER
-static char *makeprog = MAKEPROG;
-#endif
-
#ifndef WIN32 /* not used in WIN32 case */
static char *shellprog = SHELLPROG;
#endif
*/
#ifndef WIN32
const char *basic_diff_opts = "";
-const char *pretty_diff_opts = "-C3";
+const char *pretty_diff_opts = "-U3";
#else
const char *basic_diff_opts = "-w";
-const char *pretty_diff_opts = "-w -C3";
+const char *pretty_diff_opts = "-w -U3";
#endif
/* options settable from command line */
bool debug = false;
char *inputdir = ".";
char *outputdir = ".";
-char *psqldir = PGBINDIR;
+char *bindir = PGBINDIR;
char *launcher = NULL;
static _stringlist *loadlanguage = 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_install = NULL;
-static char *temp_config = NULL;
-static char *top_builddir = 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 char *dlpath = PKGLIBDIR;
static char *user = NULL;
static _stringlist *extraroles = NULL;
-static _stringlist *extra_install = NULL;
static char *config_auth_datadir = NULL;
/* internal variables */
static bool directory_exists(const char *dir);
static void make_directory(const char *dir);
-static void
-header(const char *fmt,...)
-/* This extension allows gcc to check the format string for consistency with
- the supplied arguments. */
-__attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
-static void
-status(const char *fmt,...)
-/* This extension allows gcc to check the format string for consistency with
- the supplied arguments. */
-__attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2)));
-static void
-psql_command(const char *database, const char *query,...)
-/* This extension allows gcc to check the format string for consistency with
- the supplied arguments. */
-__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
-
-#ifdef WIN32
-typedef BOOL (WINAPI * __CreateRestrictedToken) (HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE);
-
-/* Windows API define missing from some versions of MingW headers */
-#ifndef DISABLE_MAX_PRIVILEGE
-#define DISABLE_MAX_PRIVILEGE 0x1
-#endif
-#endif
+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.
void
add_stringlist_item(_stringlist **listhead, const char *str)
{
- _stringlist *newentry = malloc(sizeof(_stringlist));
+ _stringlist *newentry = pg_malloc(sizeof(_stringlist));
_stringlist *oldentry;
- newentry->str = strdup(str);
+ newentry->str = pg_strdup(str);
newentry->next = NULL;
if (*listhead == NULL)
*listhead = newentry;
static void
split_to_stringlist(const char *s, const char *delim, _stringlist **listhead)
{
- char *sc = strdup(s);
+ char *sc = pg_strdup(s);
char *token = strtok(sc, delim);
while (token)
fflush(stderr);
snprintf(buf, sizeof(buf),
- "\"%s/pg_ctl\" stop -D \"%s/data\" -s -m fast",
- bindir, temp_install);
+ "\"%s%spg_ctl\" stop -D \"%s/data\" -s",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ temp_instance);
r = system(buf);
if (r != 0)
{
static const char *
make_temp_sockdir(void)
{
- char *template = strdup("/tmp/pg_regress-XXXXXX");
+ char *template = pg_strdup("/tmp/pg_regress-XXXXXX");
temp_sockdir = mkdtemp(template);
if (temp_sockdir == NULL)
return temp_sockdir;
}
-#endif /* HAVE_UNIX_SOCKETS */
+#endif /* HAVE_UNIX_SOCKETS */
/*
* Check whether string matches pattern
* NOTE: Assumes there is enough room in the target buffer!
*/
void
-replace_string(char *string, char *replace, char *replacement)
+replace_string(char *string, const char *replace, const char *replacement)
{
char *ptr;
while ((ptr = strstr(string, replace)) != NULL)
{
- char *dup = strdup(string);
+ char *dup = pg_strdup(string);
strlcpy(string, dup, ptr - string + 1);
strcat(string, replacement);
* the given suffix.
*/
static void
-convert_sourcefiles_in(char *source_subdir, char *dest_dir, char *dest_subdir, char *suffix)
+convert_sourcefiles_in(const char *source_subdir, const char *dest_dir, const char *dest_subdir, const char *suffix)
{
char testtablespace[MAXPGPATH];
char indir[MAXPGPATH];
static void
convert_sourcefiles(void)
{
- convert_sourcefiles_in("input", inputdir, "sql", "sql");
+ convert_sourcefiles_in("input", outputdir, "sql", "sql");
convert_sourcefiles_in("output", outputdir, "expected", "out");
}
*/
if (string_matches_pattern(host_platform, platform))
{
- _resultmap *entry = malloc(sizeof(_resultmap));
+ _resultmap *entry = pg_malloc(sizeof(_resultmap));
- entry->test = strdup(buf);
- entry->type = strdup(file_type);
- entry->resultfile = strdup(expected);
+ entry->test = pg_strdup(buf);
+ entry->type = pg_strdup(file_type);
+ entry->resultfile = pg_strdup(expected);
entry->next = resultmap;
resultmap = entry;
}
putenv(s);
}
-/*
- * Set the environment variable "pathname", prepending "addval" to its
- * old value (if any).
- */
-static void
-add_to_path(const char *pathname, char separator, const char *addval)
-{
- char *oldval = getenv(pathname);
- char *newval;
-
- if (!oldval || !oldval[0])
- {
- /* no previous value */
- newval = psprintf("%s=%s", pathname, addval);
- }
- else
- newval = psprintf("%s=%s%c%s", pathname, addval, separator, oldval);
-
- putenv(newval);
-}
-
/*
* 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)
/*
* Most platforms have adopted the POSIX locale as their
* implementation-defined default locale. Exceptions include native
- * Windows, Darwin with --enable-nls, and Cygwin with --enable-nls.
+ * Windows, macOS with --enable-nls, and Cygwin with --enable-nls.
* (Use of --enable-nls matters because libintl replaces setlocale().)
- * Also, PostgreSQL does not support Darwin with locale environment
+ * Also, PostgreSQL does not support macOS with locale environment
* variables unset; see PostmasterMain().
*/
#if defined(WIN32) || defined(__CYGWIN__) || defined(__darwin__)
putenv(new_pgoptions);
}
- if (temp_install)
+ if (temp_instance)
{
/*
* Clear out any environment vars that might cause psql to connect to
sprintf(s, "%d", port);
doputenv("PGPORT", s);
}
-
- /*
- * GNU make stores some flags in the MAKEFLAGS environment variable to
- * pass arguments to its own children. If we are invoked by make,
- * that causes the make invoked by us to think its part of the make
- * task invoking us, and so it tries to communicate with the toplevel
- * make. Which fails.
- *
- * Unset the variable to protect against such problems. We also reset
- * MAKELEVEL to be certain the child doesn't notice the make above us.
- */
- unsetenv("MAKEFLAGS");
- unsetenv("MAKELEVEL");
-
- /*
- * Adjust path variables to point into the temp-install tree
- */
- bindir = psprintf("%s/install/%s", temp_install, bindir);
-
- libdir = psprintf("%s/install/%s", temp_install, libdir);
-
- datadir = psprintf("%s/install/%s", temp_install, datadir);
-
- /* psql will be installed into temp-install bindir */
- psqldir = bindir;
-
- /*
- * Set up shared library paths to include the temp install.
- *
- * LD_LIBRARY_PATH covers many platforms. DYLD_LIBRARY_PATH works on
- * Darwin, and maybe other Mach-based systems. LIBPATH is for AIX.
- * Windows needs shared libraries in PATH (only those linked into
- * executables, not dlopen'ed ones). Feel free to account for others
- * as well.
- */
- add_to_path("LD_LIBRARY_PATH", ':', libdir);
- add_to_path("DYLD_LIBRARY_PATH", ':', libdir);
- add_to_path("LIBPATH", ':', libdir);
-#if defined(WIN32)
- add_to_path("PATH", ';', libdir);
-#elif defined(__CYGWIN__)
- add_to_path("PATH", ':', libdir);
-#endif
}
else
{
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
*/
}
#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.
if (!GetTokenInformation(token, TokenUser, NULL, 0, &retlen) && GetLastError() != 122)
{
fprintf(stderr,
- _("%s: could not get token user size: error code %lu\n"),
+ _("%s: could not get token information buffer size: error code %lu\n"),
progname, GetLastError());
exit(2);
}
- tokenuser = malloc(retlen);
+ tokenuser = pg_malloc(retlen);
if (!GetTokenInformation(token, TokenUser, tokenuser, retlen, &retlen))
{
fprintf(stderr,
- _("%s: could not get token user: error code %lu\n"),
+ _("%s: could not get token information: error code %lu\n"),
progname, GetLastError());
exit(2);
}
* 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)
+config_sspi_auth(const char *pgdata, const char *superuser_name)
{
const char *accountname,
*domainname;
- const char *username;
char *errstr;
bool have_ipv6;
char fname[MAXPGPATH];
*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.
- */
+ /* Find out the name of the current OS user */
current_windows_user(&accountname, &domainname);
- username = get_user_name(&errstr);
- if (username == NULL)
+
+ /* Determine the bootstrap superuser's name */
+ if (superuser_name == NULL)
{
- fprintf(stderr, "%s: %s\n", progname, errstr);
- exit(2);
+ /*
+ * 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);
+ }
}
/*
} while (0)
res = snprintf(fname, sizeof(fname), "%s/pg_hba.conf", pgdata);
- if (res < 0 || res >= sizeof(fname) - 1)
+ if (res < 0 || res >= sizeof(fname))
{
/*
* Truncating this name is a fatal error, because we must not fail to
* '#'. 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, username) >= 0);
+ 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, sl->str) >= 0);
+ CW(fprintf(ident, "regress \"%s@%s\" %s\n",
+ accountname, domainname, fmtHba(sl->str)) >= 0);
CW(fclose(ident) == 0);
}
-#endif
+
+#endif /* ENABLE_SSPI */
/*
* Issue a command via psql, connecting to the specified database
/* And now we can build and execute the shell command */
snprintf(psql_cmd, sizeof(psql_cmd),
"\"%s%spsql\" -X -c \"%s\" \"%s\"",
- psqldir ? psqldir : "",
- psqldir ? "/" : "",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
query_escaped,
database);
/* in parent */
return pid;
#else
- char *cmdline2;
- BOOL b;
- STARTUPINFO si;
PROCESS_INFORMATION pi;
- HANDLE origToken;
+ char *cmdline2;
HANDLE restrictedToken;
- SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY};
- SID_AND_ATTRIBUTES dropSids[2];
- __CreateRestrictedToken _CreateRestrictedToken = NULL;
- HANDLE Advapi32Handle;
-
- ZeroMemory(&si, sizeof(si));
- si.cb = sizeof(si);
-
- Advapi32Handle = LoadLibrary("ADVAPI32.DLL");
- if (Advapi32Handle != NULL)
- {
- _CreateRestrictedToken = (__CreateRestrictedToken) GetProcAddress(Advapi32Handle, "CreateRestrictedToken");
- }
-
- if (_CreateRestrictedToken == NULL)
- {
- if (Advapi32Handle != NULL)
- FreeLibrary(Advapi32Handle);
- fprintf(stderr, _("%s: cannot create restricted tokens on this platform\n"),
- progname);
- exit(2);
- }
-
- /* Open the current token to use as base for the restricted one */
- if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &origToken))
- {
- fprintf(stderr, _("could not open process token: error code %lu\n"),
- GetLastError());
- exit(2);
- }
-
- /* Allocate list of SIDs to remove */
- ZeroMemory(&dropSids, sizeof(dropSids));
- if (!AllocateAndInitializeSid(&NtAuthority, 2,
- SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, &dropSids[0].Sid) ||
- !AllocateAndInitializeSid(&NtAuthority, 2,
- SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_POWER_USERS, 0, 0, 0, 0, 0, 0, &dropSids[1].Sid))
- {
- fprintf(stderr, _("could not allocate SIDs: error code %lu\n"), GetLastError());
- exit(2);
- }
-
- b = _CreateRestrictedToken(origToken,
- DISABLE_MAX_PRIVILEGE,
- sizeof(dropSids) / sizeof(dropSids[0]),
- dropSids,
- 0, NULL,
- 0, NULL,
- &restrictedToken);
-
- FreeSid(dropSids[1].Sid);
- FreeSid(dropSids[0].Sid);
- CloseHandle(origToken);
- FreeLibrary(Advapi32Handle);
-
- if (!b)
- {
- fprintf(stderr, _("could not create restricted token: error code %lu\n"),
- GetLastError());
- exit(2);
- }
+ memset(&pi, 0, sizeof(pi));
cmdline2 = psprintf("cmd /c \"%s\"", cmdline);
-#ifndef __CYGWIN__
- AddUserToTokenDacl(restrictedToken);
-#endif
-
- if (!CreateProcessAsUser(restrictedToken,
- NULL,
- cmdline2,
- NULL,
- NULL,
- TRUE,
- CREATE_SUSPENDED,
- NULL,
- NULL,
- &si,
- &pi))
- {
- fprintf(stderr, _("could not start process for \"%s\": error code %lu\n"),
- cmdline2, GetLastError());
+ if ((restrictedToken =
+ CreateRestrictedProcess(cmdline2, &pi)) == 0)
exit(2);
- }
- free(cmdline2);
-
- ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
return pi.hProcess;
#endif
if (platform_expectfile)
{
/*
- * Replace everything afer the last slash in expectfile with what the
+ * Replace everything after the last slash in expectfile with what the
* platform_expectfile contains.
*/
char *p = strrchr(expectfile, '/');
* Use the best comparison file to generate the "pretty" diff, which we
* append to the diffs summary file.
*/
- snprintf(cmd, sizeof(cmd),
- "diff %s \"%s\" \"%s\" >> \"%s\"",
- pretty_diff_opts, best_expect_file, resultsfile, difffilename);
- run_diff(cmd, difffilename);
- /* And append a separator */
+ /* Write diff header */
difffile = fopen(difffilename, "a");
if (difffile)
{
fprintf(difffile,
- "\n======================================================================\n\n");
+ "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[]
+ * 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, char **names, int num_tests)
+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 = malloc(num_tests * sizeof(PID_TYPE));
+ PID_TYPE *active_pids = pg_malloc(num_tests * sizeof(PID_TYPE));
memcpy(active_pids, pids, num_tests * sizeof(PID_TYPE));
#endif
p = active_pids[r - WAIT_OBJECT_0];
/* compact the active_pids array */
active_pids[r - WAIT_OBJECT_0] = active_pids[tests_left - 1];
-#endif /* WIN32 */
+#endif /* WIN32 */
for (i = 0; i < num_tests; i++)
{
#endif
pids[i] = INVALID_PID;
statuses[i] = (int) exit_status;
+ INSTR_TIME_SET_CURRENT(stoptimes[i]);
if (names)
status(" %s", names[i]);
tests_left--;
#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));
+ status(_(" (test process was terminated by signal %d: %s)"),
+ WTERMSIG(exitstatus), pg_strsignal(WTERMSIG(exitstatus)));
#endif
}
else
_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(resultfiles, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
- memset(expectfiles, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
- memset(tags, 0, sizeof(_stringlist *) * MAX_PARALLEL_TESTS);
+ 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)
line_num++;
- for (i = 0; i < MAX_PARALLEL_TESTS; i++)
- {
- if (resultfiles[i] == NULL)
- break;
- free_stringlist(&resultfiles[i]);
- free_stringlist(&expectfiles[i]);
- free_stringlist(&tags[i]);
- }
-
/* strip trailing whitespace, especially the newline */
i = strlen(scbuf);
while (i > 0 && isspace((unsigned char) scbuf[i - 1]))
num_tests = 0;
inword = false;
- for (c = test; *c; c++)
+ for (c = test;; c++)
{
- if (isspace((unsigned char) *c))
+ if (*c == '\0' || isspace((unsigned char) *c))
{
- *c = '\0';
- inword = false;
+ 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)
{
- 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++;
+ /* Start of a test name */
+ test = c;
inword = true;
}
}
if (num_tests == 1)
{
- status(_("test %-24s ... "), tests[0]);
+ status(_("test %-28s ... "), tests[0]);
pids[0] = (tfunc) (tests[0], &resultfiles[0], &expectfiles[0], &tags[0]);
- wait_for_tests(pids, statuses, NULL, 1);
+ 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;
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();
}
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, tests, num_tests);
+ wait_for_tests(pids, statuses, stoptimes, tests, num_tests);
status_end();
}
bool differ = false;
if (num_tests > 1)
- status(_(" %-24s ... "), tests[i]);
+ status(_(" %-28s ... "), tests[i]);
/*
* Advance over all three lists simultaneously.
*/
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)
+ rl = rl->next, el = el->next,
+ tl = tl ? tl->next : NULL)
{
bool newdiff;
- if (tl)
- tl = tl->next; /* tl has the same length as rl and el
- * if it exists */
-
newdiff = results_differ(tests[i], rl->str, el->str);
if (newdiff && tl)
{
}
else
{
- status(_("ok"));
+ 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);
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;
*tl;
bool differ = false;
- status(_("test %-24s ... "), test);
+ status(_("test %-28s ... "), test);
pid = (tfunc) (test, &resultfiles, &expectfiles, &tags);
- wait_for_tests(&pid, &exit_status, NULL, 1);
+ INSTR_TIME_SET_CURRENT(starttime);
+ wait_for_tests(&pid, &exit_status, &stoptime, NULL, 1);
/*
* Advance over all three lists simultaneously.
*/
for (rl = resultfiles, el = expectfiles, tl = tags;
rl != NULL; /* rl and el have the same length */
- rl = rl->next, el = el->next)
+ rl = rl->next, el = el->next,
+ tl = tl ? tl->next : NULL)
{
bool newdiff;
- if (tl)
- tl = tl->next; /* tl has the same length as rl and el if it
- * exists */
-
newdiff = results_differ(test, rl->str, el->str);
if (newdiff && tl)
{
}
else
{
- status(_("ok"));
+ 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();
}
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 = strdup(file);
+ logfilename = pg_strdup(file);
logfile = fopen(logfilename, "w");
if (!logfile)
{
/* create the diffs file as empty */
snprintf(file, sizeof(file), "%s/regression.diffs", outputdir);
- difffilename = strdup(file);
+ difffilename = pg_strdup(file);
difffile = fopen(difffilename, "w");
if (!difffile)
{
/* we don't keep the diffs file open continuously */
fclose(difffile);
- /* also create the output directory if not present */
+ /* also create the results directory if not present */
snprintf(file, sizeof(file), "%s/results", outputdir);
if (!directory_exists(file))
make_directory(file);
"ALTER DATABASE \"%s\" SET lc_monetary TO 'C';"
"ALTER DATABASE \"%s\" SET lc_numeric TO 'C';"
"ALTER DATABASE \"%s\" SET lc_time TO 'C';"
- "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';",
- dbname, dbname, dbname, dbname, dbname);
+ "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 procedural languages. We use CREATE OR REPLACE
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-install=DIR create a temporary installation in DIR\n"));
- printf(_(" --use-existing use an existing installation\n"));
+ printf(_(" --bindir=BINPATH use BINPATH for programs that are run;\n"));
+ printf(_(" if empty, use PATH from the environment\n"));
+ printf(_(" --config-auth=DATADIR update authentication settings for DATADIR\n"));
+ printf(_(" --create-role=ROLE create the specified role before testing\n"));
+ printf(_(" --dbname=DB use database DB (default \"regression\")\n"));
+ printf(_(" --debug turn on debug mode in programs that are run\n"));
+ printf(_(" --dlpath=DIR look for dynamic libraries in DIR\n"));
+ printf(_(" --encoding=ENCODING use ENCODING as the encoding\n"));
+ printf(_(" -h, --help show this help, then exit\n"));
+ printf(_(" --inputdir=DIR take input files from DIR (default \".\")\n"));
+ printf(_(" --launcher=CMD use CMD as launcher of psql\n"));
+ printf(_(" --load-extension=EXT load the named extension before running the\n"));
+ printf(_(" tests; can appear multiple times\n"));
+ printf(_(" --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(_(" --max-concurrent-tests=N maximum number of concurrent tests in schedule\n"));
+ printf(_(" (default is 0, meaning unlimited)\n"));
+ printf(_(" --outputdir=DIR place output files in DIR (default \".\")\n"));
+ printf(_(" --schedule=FILE use test ordering schedule from FILE\n"));
+ printf(_(" (can be used multiple times to concatenate)\n"));
+ printf(_(" --temp-instance=DIR create a temporary instance in DIR\n"));
+ printf(_(" --use-existing use an existing installation\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
printf(_("\n"));
- printf(_("Options for \"temp-install\" mode:\n"));
- printf(_(" --extra-install=DIR additional directory to install (e.g., contrib)\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(_(" --top-builddir=DIR (relative) path to top level build directory\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(_(" --psqldir=DIR use psql in DIR (default: configured bindir)\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"));
+ printf(_("Report bugs to <pgsql-bugs@lists.postgresql.org>.\n"));
}
int
{"encoding", required_argument, NULL, 6},
{"outputdir", required_argument, NULL, 7},
{"schedule", required_argument, NULL, 8},
- {"temp-install", required_argument, NULL, 9},
+ {"temp-instance", required_argument, NULL, 9},
{"no-locale", no_argument, NULL, 10},
- {"top-builddir", required_argument, NULL, 11},
{"host", required_argument, NULL, 13},
{"port", required_argument, NULL, 14},
{"user", required_argument, NULL, 15},
- {"psqldir", required_argument, NULL, 16},
+ {"bindir", required_argument, NULL, 16},
{"dlpath", required_argument, NULL, 17},
{"create-role", required_argument, NULL, 18},
{"temp-config", required_argument, NULL, 19},
{"use-existing", no_argument, NULL, 20},
{"launcher", required_argument, NULL, 21},
{"load-extension", required_argument, NULL, 22},
- {"extra-install", required_argument, NULL, 23},
{"config-auth", required_argument, NULL, 24},
+ {"max-concurrent-tests", required_argument, NULL, 25},
{NULL, 0, NULL, 0}
};
char buf[MAXPGPATH * 4];
char buf2[MAXPGPATH * 4];
+ pg_logging_init(argv[0]);
progname = get_progname(argv[0]);
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_regress"));
+ get_restricted_token();
+
atexit(stop_postmaster);
#ifndef HAVE_UNIX_SOCKETS
* before we add the specified one.
*/
free_stringlist(&dblist);
- split_to_stringlist(strdup(optarg), ", ", &dblist);
+ split_to_stringlist(optarg, ",", &dblist);
break;
case 2:
debug = true;
break;
case 3:
- inputdir = strdup(optarg);
+ inputdir = pg_strdup(optarg);
break;
case 4:
add_stringlist_item(&loadlanguage, optarg);
max_connections = atoi(optarg);
break;
case 6:
- encoding = strdup(optarg);
+ encoding = pg_strdup(optarg);
break;
case 7:
- outputdir = strdup(optarg);
+ outputdir = pg_strdup(optarg);
break;
case 8:
add_stringlist_item(&schedulelist, optarg);
break;
case 9:
- temp_install = make_absolute_path(optarg);
+ temp_instance = make_absolute_path(optarg);
break;
case 10:
nolocale = true;
break;
- case 11:
- top_builddir = strdup(optarg);
- break;
case 13:
- hostname = strdup(optarg);
+ hostname = pg_strdup(optarg);
break;
case 14:
port = atoi(optarg);
port_specified_by_user = true;
break;
case 15:
- user = strdup(optarg);
+ user = pg_strdup(optarg);
break;
case 16:
- /* "--psqldir=" should mean to use PATH */
+ /* "--bindir=" means to use PATH */
if (strlen(optarg))
- psqldir = strdup(optarg);
+ bindir = pg_strdup(optarg);
+ else
+ bindir = NULL;
break;
case 17:
- dlpath = strdup(optarg);
+ dlpath = pg_strdup(optarg);
break;
case 18:
- split_to_stringlist(strdup(optarg), ", ", &extraroles);
+ split_to_stringlist(optarg, ",", &extraroles);
break;
case 19:
- temp_config = strdup(optarg);
+ add_stringlist_item(&temp_configs, optarg);
break;
case 20:
use_existing = true;
break;
case 21:
- launcher = strdup(optarg);
+ launcher = pg_strdup(optarg);
break;
case 22:
add_stringlist_item(&loadextension, optarg);
break;
- case 23:
- add_stringlist_item(&extra_install, optarg);
- break;
case 24:
- config_auth_datadir = pstrdup(optarg);
+ config_auth_datadir = pg_strdup(optarg);
+ break;
+ case 25:
+ max_concurrent_tests = atoi(optarg);
break;
default:
/* getopt_long already emitted a complaint */
if (config_auth_datadir)
{
#ifdef ENABLE_SSPI
- config_sspi_auth(config_auth_datadir);
+ config_sspi_auth(config_auth_datadir, user);
#endif
exit(0);
}
- if (temp_install && !port_specified_by_user)
+ if (temp_instance && !port_specified_by_user)
/*
* To reduce chances of interference with parallel installations, use
unlimit_core_size();
#endif
- if (temp_install)
+ if (temp_instance)
{
FILE *pg_conf;
- _stringlist *sl;
+ const char *env_wait;
+ int wait_seconds;
/*
- * Prepare the temp installation
+ * Prepare the temp instance
*/
- if (!top_builddir)
- {
- fprintf(stderr, _("--top-builddir must be specified when using --temp-install\n"));
- exit(2);
- }
- if (directory_exists(temp_install))
+ if (directory_exists(temp_instance))
{
- header(_("removing existing temp installation"));
- if (!rmtree(temp_install, true))
+ header(_("removing existing temp instance"));
+ if (!rmtree(temp_instance, true))
{
- fprintf(stderr, _("\n%s: could not remove temp installation \"%s\"\n"),
- progname, temp_install);
+ fprintf(stderr, _("\n%s: could not remove temp instance \"%s\"\n"),
+ progname, temp_instance);
exit(2);
}
}
- header(_("creating temporary installation"));
+ header(_("creating temporary instance"));
- /* make the temp install top directory */
- make_directory(temp_install);
+ /* 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);
- /* "make install" */
-#ifndef WIN32_ONLY_COMPILER
- snprintf(buf, sizeof(buf),
- "\"%s\" -C \"%s\" DESTDIR=\"%s/install\" install > \"%s/log/install.log\" 2>&1",
- makeprog, top_builddir, temp_install, outputdir);
-#else
- snprintf(buf, sizeof(buf),
- "perl \"%s/src/tools/msvc/install.pl\" \"%s/install\" >\"%s/log/install.log\" 2>&1",
- top_builddir, temp_install, outputdir);
-#endif
- if (system(buf))
- {
- fprintf(stderr, _("\n%s: installation failed\nExamine %s/log/install.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
- exit(2);
- }
-
- for (sl = extra_install; sl != NULL; sl = sl->next)
- {
-#ifndef WIN32_ONLY_COMPILER
- snprintf(buf, sizeof(buf),
- "\"%s\" -C \"%s/%s\" DESTDIR=\"%s/install\" install >> \"%s/log/install.log\" 2>&1",
- makeprog, top_builddir, sl->str, temp_install, outputdir);
-#else
- fprintf(stderr, _("\n%s: --extra-install option not supported on this platform\n"), progname);
- exit(2);
-#endif
-
- if (system(buf))
- {
- fprintf(stderr, _("\n%s: installation failed\nExamine %s/log/install.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
- exit(2);
- }
- }
-
/* initdb */
header(_("initializing database system"));
snprintf(buf, sizeof(buf),
- "\"%s/initdb\" -D \"%s/data\" -L \"%s\" --noclean --nosync%s%s > \"%s/log/initdb.log\" 2>&1",
- bindir, temp_install, datadir,
+ "\"%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);
* failures, don't set max_prepared_transactions any higher than
* actually needed by the prepared_xacts regression test.)
*/
- snprintf(buf, sizeof(buf), "%s/data/postgresql.conf", temp_install);
+ snprintf(buf, sizeof(buf), "%s/data/postgresql.conf", temp_instance);
pg_conf = fopen(buf, "a");
if (pg_conf == NULL)
{
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);
- if (temp_config != NULL)
+ for (sl = temp_configs; sl != NULL; sl = sl->next)
{
+ char *temp_config = sl->str;
FILE *extra_conf;
char line_buf[1024];
* Since we successfully used the same buffer for the much-longer
* "initdb" command, this can't truncate.
*/
- snprintf(buf, sizeof(buf), "%s/data", temp_install);
- config_sspi_auth(buf);
+ snprintf(buf, sizeof(buf), "%s/data", temp_instance);
+ config_sspi_auth(buf, NULL);
#elif !defined(HAVE_UNIX_SOCKETS)
#error Platform has no means to secure the test installation.
#endif
* Check if there is a postmaster running already.
*/
snprintf(buf2, sizeof(buf2),
- "\"%s/psql\" -X postgres <%s 2>%s",
- bindir, DEVNULL, DEVNULL);
+ "\"%s%spsql\" -X postgres <%s 2>%s",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ DEVNULL, DEVNULL);
for (i = 0; i < 16; i++)
{
*/
header(_("starting postmaster"));
snprintf(buf, sizeof(buf),
- "\"%s/postgres\" -D \"%s/data\" -F%s "
+ "\"%s%spostgres\" -D \"%s/data\" -F%s "
"-c \"listen_addresses=%s\" -k \"%s\" "
"> \"%s/log/postmaster.log\" 2>&1",
- bindir, temp_install, debug ? " -d 5" : "",
+ bindir ? bindir : "",
+ bindir ? "/" : "",
+ temp_instance, debug ? " -d 5" : "",
hostname ? hostname : "", sockdir ? sockdir : "",
outputdir);
postmaster_pid = spawn_process(buf);
}
/*
- * Wait till postmaster is able to accept connections (normally only a
- * second or so, but Cygwin is reportedly *much* slower). Don't wait
- * forever, however.
+ * Wait till postmaster is able to accept connections; normally 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.
*/
- for (i = 0; i < 60; i++)
+ 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)
* Fail immediately if postmaster has exited
*/
#ifndef WIN32
- if (kill(postmaster_pid, 0) != 0)
+ if (waitpid(postmaster_pid, NULL, WNOHANG) == postmaster_pid)
#else
if (WaitForSingleObject(postmaster_pid, 0) == WAIT_OBJECT_0)
#endif
pg_usleep(1000000L);
}
- if (i >= 60)
+ if (i >= wait_seconds)
{
- fprintf(stderr, _("\n%s: postmaster did not respond within 60 seconds\nExamine %s/log/postmaster.log for the reason\n"), progname, outputdir);
+ fprintf(stderr, _("\n%s: postmaster did not respond within %d seconds\nExamine %s/log/postmaster.log for the reason\n"),
+ progname, wait_seconds, outputdir);
/*
* If we get here, the postmaster is probably wedged somewhere in
postmaster_running = true;
-#ifdef WIN64
+#ifdef _WIN64
/* need a series of two casts to convert HANDLE without compiler warning */
#define ULONGPID(x) (unsigned long) (unsigned long long) (x)
#else
/*
* Shut down temp installation's postmaster
*/
- if (temp_install)
+ if (temp_instance)
{
header(_("shutting down postmaster"));
stop_postmaster();
}
/*
- * If there were no errors, remove the temp installation immediately to
- * conserve disk space. (If there were errors, we leave the installation
- * in place for possible manual investigation.)
+ * If there were no errors, remove the temp instance immediately to
+ * conserve disk space. (If there were errors, we leave the instance in
+ * place for possible manual investigation.)
*/
- if (temp_install && fail_count == 0 && fail_ignore_count == 0)
+ if (temp_instance && fail_count == 0 && fail_ignore_count == 0)
{
- header(_("removing temporary installation"));
- if (!rmtree(temp_install, true))
- fprintf(stderr, _("\n%s: could not remove temp installation \"%s\"\n"),
- progname, temp_install);
+ 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);