X-Git-Url: https://granicus.if.org/sourcecode?a=blobdiff_plain;f=src%2Ftest%2Fregress%2Fpg_regress.c;h=1411ca4e40054e6c983676c4d0eee21fbd2e591c;hb=c02d5b7c27d740830379244db4b9ef111bbf0fc8;hp=0103b8868a2ca2704c5fb499f7f635e557ca5dd4;hpb=7ce9b3683ea76233c33e550110c5a63ecd8add89;p=postgresql diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index 0103b8868a..1411ca4e40 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -8,15 +8,15 @@ * * This code is released under the terms of the PostgreSQL License. * - * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group + * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/test/regress/pg_regress.c,v 1.32 2007/05/31 15:13:06 petere Exp $ + * src/test/regress/pg_regress.c * *------------------------------------------------------------------------- */ -#include "postgres_fe.h" +#include "pg_regress.h" #include #include @@ -32,26 +32,11 @@ #include "getopt_long.h" #include "pg_config_paths.h" -#ifndef WIN32 -#define PID_TYPE pid_t -#define INVALID_PID (-1) -#else -#define PID_TYPE HANDLE -#define INVALID_PID INVALID_HANDLE_VALUE -#endif - - -/* simple list of strings */ -typedef struct _stringlist -{ - char *str; - struct _stringlist *next; -} _stringlist; - /* for resultmap we need a list of pairs of strings */ typedef struct _resultmap { char *test; + char *type; char *resultfile; struct _resultmap *next; } _resultmap; @@ -62,11 +47,16 @@ typedef struct _resultmap * 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. */ -static char *bindir = PGBINDIR; -static char *libdir = LIBDIR; -static char *datadir = PGSHAREDIR; -static char *host_platform = HOST_TUPLE; +char *bindir = PGBINDIR; +char *libdir = LIBDIR; +char *datadir = PGSHAREDIR; +char *host_platform = HOST_TUPLE; + #ifndef WIN32_ONLY_COMPILER static char *makeprog = MAKEPROG; #endif @@ -75,29 +65,44 @@ static char *makeprog = MAKEPROG; static char *shellprog = SHELLPROG; #endif -/* currently we can use the same diff switches on all platforms */ -static const char *basic_diff_opts = "-w"; -static const char *pretty_diff_opts = "-w -C3"; +/* + * 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 */ -static char *dbname = "regression"; -static bool debug = false; -static char *inputdir = "."; -static char *outputdir = "."; +_stringlist *dblist = NULL; +bool debug = false; +char *inputdir = "."; +char *outputdir = "."; +char *psqldir = PGBINDIR; +char *launcher = NULL; static _stringlist *loadlanguage = NULL; +static _stringlist *loadextension = NULL; static int max_connections = 0; static char *encoding = NULL; static _stringlist *schedulelist = NULL; static _stringlist *extra_tests = NULL; static char *temp_install = NULL; +static char *temp_config = NULL; static char *top_builddir = NULL; -static int temp_port = 65432; static bool nolocale = false; -static char *psqldir = NULL; +static bool use_existing = false; static char *hostname = NULL; static int port = -1; +static bool port_specified_by_user = false; +static char *dlpath = PKGLIBDIR; static char *user = NULL; -static char *srcdir = NULL; +static _stringlist *extraroles = NULL; +static _stringlist *extra_install = NULL; /* internal variables */ static const char *progname; @@ -114,55 +119,56 @@ 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 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(printf, 1, 2))); +__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(printf, 1, 2))); +__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(printf, 2, 3))); +__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); +typedef BOOL (WINAPI * __CreateRestrictedToken) (HANDLE, DWORD, DWORD, PSID_AND_ATTRIBUTES, DWORD, PLUID_AND_ATTRIBUTES, DWORD, PSID_AND_ATTRIBUTES, PHANDLE); -/* Windows API define missing from MingW headers */ +/* Windows API define missing from some versions of MingW headers */ +#ifndef DISABLE_MAX_PRIVILEGE #define DISABLE_MAX_PRIVILEGE 0x1 #endif +#endif /* * allow core files if possible. */ #if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE) -static void +static void unlimit_core_size(void) { struct rlimit lim; - getrlimit(RLIMIT_CORE,&lim); + + getrlimit(RLIMIT_CORE, &lim); if (lim.rlim_max == 0) { fprintf(stderr, - _("%s: cannot set core size,: disallowed by hard limit.\n"), + _("%s: could not set core size: disallowed by hard limit\n"), progname); return; } else if (lim.rlim_max == RLIM_INFINITY || lim.rlim_cur < lim.rlim_max) { lim.rlim_cur = lim.rlim_max; - setrlimit(RLIMIT_CORE,&lim); - } + setrlimit(RLIMIT_CORE, &lim); + } } #endif @@ -170,7 +176,7 @@ unlimit_core_size(void) /* * Add an item at the end of a stringlist. */ -static void +void add_stringlist_item(_stringlist ** listhead, const char *str) { _stringlist *newentry = malloc(sizeof(_stringlist)); @@ -188,6 +194,38 @@ add_stringlist_item(_stringlist ** listhead, const char *str) } } +/* + * 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 = strdup(s); + char *token = strtok(sc, delim); + + while (token) + { + add_stringlist_item(listhead, token); + token = strtok(NULL, delim); + } + free(sc); +} + /* * Print a progress banner on stdout. */ @@ -248,6 +286,7 @@ stop_postmaster(void) { /* We use pg_ctl to issue the kill and wait for stop */ char buf[MAXPGPATH * 2]; + int r; /* On Windows, system() seems not to force fflush, so... */ fflush(stdout); @@ -256,7 +295,14 @@ stop_postmaster(void) snprintf(buf, sizeof(buf), SYSTEMQUOTE "\"%s/pg_ctl\" stop -D \"%s/data\" -s -m fast" SYSTEMQUOTE, bindir, temp_install); - system(buf); /* ignore exit status */ + 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_nicely(), that would be recursive */ + } + postmaster_running = false; } } @@ -265,7 +311,7 @@ stop_postmaster(void) * Always exit through here, not through plain exit(), to ensure we make * an effort to shut down a temp postmaster */ -static void +void exit_nicely(int code) { stop_postmaster(); @@ -349,14 +395,14 @@ string_matches_pattern(const char *str, const char *pattern) * Replace all occurances of a string in a string with a different string. * NOTE: Assumes there is enough room in the target buffer! */ -static void +void replace_string(char *string, char *replace, char *replacement) { - char *ptr; + char *ptr; - while ((ptr = strstr(string, replace)) != NULL) + while ((ptr = strstr(string, replace)) != NULL) { - char *dup = strdup(string); + char *dup = strdup(string); strlcpy(string, dup, ptr - string + 1); strcat(string, replacement); @@ -372,66 +418,62 @@ replace_string(char *string, char *replace, char *replacement) * the given suffix. */ static void -convert_sourcefiles_in(char *source, char *dest, char *suffix) +convert_sourcefiles_in(char *source_subdir, char *dest_subdir, char *suffix) { - char abs_srcdir[MAXPGPATH]; - char abs_builddir[MAXPGPATH]; - char testtablespace[MAXPGPATH]; - char indir[MAXPGPATH]; - char **name; - char **names; - int count = 0; -#ifdef WIN32 - char *c; -#endif + char testtablespace[MAXPGPATH]; + char indir[MAXPGPATH]; + struct stat st; + int ret; + char **name; + char **names; + int count = 0; - if (!getcwd(abs_builddir, sizeof(abs_builddir))) + 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)) { - fprintf(stderr, _("%s: could not get current directory: %s\n"), - progname, strerror(errno)); - exit_nicely(2); + /* + * No warning, to avoid noise in tests that do not have these + * directories; for example, ecpg, contrib and src/pl. + */ + return; } - /* - * in a VPATH build, use the provided source directory; otherwise, use - * the current directory. - */ - if (srcdir) - strcpy(abs_srcdir, srcdir); - else - strcpy(abs_srcdir, abs_builddir); - - snprintf(indir, MAXPGPATH, "%s/%s", abs_srcdir, source); names = pgfnames(indir); if (!names) /* Error logged in pgfnames */ exit_nicely(2); + snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir); + #ifdef WIN32 - /* in Win32, replace backslashes with forward slashes */ - for (c = abs_builddir; *c; c++) - if (*c == '\\') - *c = '/'; - for (c = abs_srcdir; *c; c++) - if (*c == '\\') - *c = '/'; -#endif - /* try to create the test tablespace dir if it doesn't exist */ - snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", abs_builddir); + /* + * 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)) rmtree(testtablespace, true); make_directory(testtablespace); +#endif /* finally loop on each file and do the replacement */ for (name = names; *name; name++) { - char srcfile[MAXPGPATH]; - char destfile[MAXPGPATH]; - char prefix[MAXPGPATH]; - FILE *infile, - *outfile; - char line[1024]; + 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) @@ -444,7 +486,7 @@ convert_sourcefiles_in(char *source, char *dest, char *suffix) /* 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", dest, prefix, suffix); + snprintf(destfile, MAXPGPATH, "%s/%s.%s", dest_subdir, prefix, suffix); infile = fopen(srcfile, "r"); if (!infile) @@ -457,14 +499,15 @@ convert_sourcefiles_in(char *source, char *dest, char *suffix) if (!outfile) { fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"), - progname, destfile, strerror(errno)); + progname, destfile, strerror(errno)); exit_nicely(2); } while (fgets(line, sizeof(line), infile)) { - replace_string(line, "@abs_srcdir@", abs_srcdir); - replace_string(line, "@abs_builddir@", abs_builddir); + 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); } @@ -474,32 +517,24 @@ convert_sourcefiles_in(char *source, char *dest, char *suffix) /* * If we didn't process any files, complain because it probably means - * somebody neglected to pass the needed --srcdir argument. + * somebody neglected to pass the needed --inputdir argument. */ if (count <= 0) { - fprintf(stderr, _("%s: no *.source files found in %s\n"), + fprintf(stderr, _("%s: no *.source files found in \"%s\"\n"), progname, indir); exit_nicely(2); } - - pgfnames_cleanup(names); + + pgfnames_cleanup(names); } /* Create the .sql and .out files from the .source files, if any */ static void convert_sourcefiles(void) { - struct stat st; - int ret; - - ret = stat("input", &st); - if (ret == 0 && S_ISDIR(st.st_mode)) - convert_sourcefiles_in("input", "sql", "sql"); - - ret = stat("output", &st); - if (ret == 0 && S_ISDIR(st.st_mode)) - convert_sourcefiles_in("output", "expected", "out"); + convert_sourcefiles_in("input", "sql", "sql"); + convert_sourcefiles_in("output", "expected", "out"); } /* @@ -537,6 +572,7 @@ load_resultmap(void) while (fgets(buf, sizeof(buf), f)) { char *platform; + char *file_type; char *expected; int i; @@ -546,7 +582,16 @@ load_resultmap(void) buf[--i] = '\0'; /* parse out the line fields */ - platform = strchr(buf, '/'); + file_type = strchr(buf, ':'); + if (!file_type) + { + fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"), + buf); + exit_nicely(2); + } + *file_type++ = '\0'; + + platform = strchr(file_type, ':'); if (!platform) { fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"), @@ -574,6 +619,7 @@ load_resultmap(void) _resultmap *entry = malloc(sizeof(_resultmap)); entry->test = strdup(buf); + entry->type = strdup(file_type); entry->resultfile = strdup(expected); entry->next = resultmap; resultmap = entry; @@ -582,6 +628,36 @@ load_resultmap(void) fclose(f); } +/* + * Check in resultmap if we should be looking at a different file + */ +static +const char * +get_expectfile(const char *testname, const char *file) +{ + char *file_type; + _resultmap *rm; + + /* + * Determine the file type from the file name. This is just what is + * following the last dot in the file name. + */ + if (!file || !(file_type = strrchr(file, '.'))) + return NULL; + + file_type++; + + for (rm = resultmap; rm != NULL; rm = rm->next) + { + if (strcmp(testname, rm->test) == 0 && strcmp(file_type, rm->type) == 0) + { + return rm->resultfile; + } + } + + return NULL; +} + /* * Handy subroutine for setting an environment variable "var" to "val" */ @@ -626,25 +702,35 @@ initialize_environment(void) { char *tmp; + 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"); + /* On Windows the default locale cannot be English, so force it */ +#if defined(WIN32) || defined(__CYGWIN__) + putenv("LANG=en"); +#endif + } + /* - * Clear out any non-C locale settings + * 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("LC_COLLATE"); - unsetenv("LC_CTYPE"); - unsetenv("LC_MONETARY"); - unsetenv("LC_MESSAGES"); - unsetenv("LC_NUMERIC"); - unsetenv("LC_TIME"); - unsetenv("LC_ALL"); - unsetenv("LANG"); unsetenv("LANGUAGE"); - /* On Windows the default locale cannot be English, so force it */ -#if defined(WIN32) || defined(__CYGWIN__) - putenv("LANG=en"); -#endif + unsetenv("LC_ALL"); + putenv("LC_MESSAGES=C"); /* - * Set multibyte as requested + * Set encoding as requested */ if (encoding) doputenv("PGCLIENTENCODING", encoding); @@ -657,6 +743,23 @@ initialize_environment(void) 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 = malloc(strlen(old_pgoptions) + strlen(my_pgoptions) + 12); + sprintf(new_pgoptions, "PGOPTIONS=%s %s", old_pgoptions, my_pgoptions); + putenv(new_pgoptions); + } + if (temp_install) { /* @@ -717,8 +820,10 @@ initialize_environment(void) add_to_path("LD_LIBRARY_PATH", ':', libdir); add_to_path("DYLD_LIBRARY_PATH", ':', libdir); add_to_path("LIBPATH", ':', libdir); -#if defined(WIN32) || defined(__CYGWIN__) +#if defined(WIN32) add_to_path("PATH", ';', libdir); +#elif defined(__CYGWIN__) + add_to_path("PATH", ':', libdir); #endif } else @@ -820,7 +925,7 @@ psql_command(const char *database, const char *query,...) * * Returns the process ID (or HANDLE) so we can wait for it later */ -static PID_TYPE +PID_TYPE spawn_process(const char *cmdline) { #ifndef WIN32 @@ -854,7 +959,7 @@ spawn_process(const char *cmdline) char *cmdline2 = malloc(strlen(cmdline) + 6); sprintf(cmdline2, "exec %s", cmdline); - execl(shellprog, shellprog, "-c", cmdline2, NULL); + execl(shellprog, shellprog, "-c", cmdline2, (char *) NULL); fprintf(stderr, _("%s: could not exec \"%s\": %s\n"), progname, shellprog, strerror(errno)); exit(1); /* not exit_nicely here... */ @@ -863,123 +968,105 @@ spawn_process(const char *cmdline) return pid; #else char *cmdline2; - BOOL b; + BOOL b; STARTUPINFO si; PROCESS_INFORMATION pi; - HANDLE origToken; - HANDLE restrictedToken; + HANDLE origToken; + HANDLE restrictedToken; SID_IDENTIFIER_AUTHORITY NtAuthority = {SECURITY_NT_AUTHORITY}; SID_AND_ATTRIBUTES dropSids[2]; __CreateRestrictedToken _CreateRestrictedToken = NULL; - HANDLE Advapi32Handle; + 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, "ERROR: cannot create restricted tokens on this platform\n"); - exit_nicely(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: %lu\n", GetLastError()); - exit_nicely(2); - } + _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_nicely(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: %lu\n"), + GetLastError()); + exit_nicely(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: %lu\n", GetLastError()); - exit_nicely(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: %lu\n"), GetLastError()); + exit_nicely(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: %lu\n", GetLastError()); - exit_nicely(2); - } + 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: %lu\n"), + GetLastError()); + exit_nicely(2); + } cmdline2 = malloc(strlen(cmdline) + 8); sprintf(cmdline2, "cmd /c %s", cmdline); - if (!CreateProcessAsUser(restrictedToken, NULL, cmdline2, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) +#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\": %lu\n"), cmdline2, GetLastError()); exit_nicely(2); } + free(cmdline2); + ResumeThread(pi.hThread); CloseHandle(pi.hThread); return pi.hProcess; #endif } -/* - * start a psql test process for specified file (including redirection), - * and return process ID - */ -static PID_TYPE -psql_start_test(const char *testname) -{ - PID_TYPE pid; - char infile[MAXPGPATH]; - char outfile[MAXPGPATH]; - char psql_cmd[MAXPGPATH * 3]; - - snprintf(infile, sizeof(infile), "%s/sql/%s.sql", - inputdir, testname); - snprintf(outfile, sizeof(outfile), "%s/results/%s.out", - outputdir, testname); - - snprintf(psql_cmd, sizeof(psql_cmd), - SYSTEMQUOTE "\"%s%spsql\" -X -a -q -d \"%s\" < \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE, - psqldir ? psqldir : "", - psqldir ? "/" : "", - dbname, - infile, - outfile); - - pid = spawn_process(psql_cmd); - - if (pid == INVALID_PID) - { - fprintf(stderr, _("could not start process for test %s\n"), - testname); - exit_nicely(2); - } - - return pid; -} - /* * Count bytes in file */ @@ -1026,7 +1113,7 @@ file_line_count(const char *file) return l; } -static bool +bool file_exists(const char *file) { FILE *f = fopen(file, "r"); @@ -1044,7 +1131,7 @@ directory_exists(const char *dir) if (stat(dir, &st) != 0) return false; - if (st.st_mode & S_IFDIR) + if (S_ISDIR(st.st_mode)) return true; return false; } @@ -1061,6 +1148,31 @@ make_directory(const char *dir) } } +/* + * In: filename.ext, Return: filename_i.ext, where 0 < i <= 9 + */ +static char * +get_alternative_expectfile(const char *expectfile, int i) +{ + char *last_dot; + int ssize = strlen(expectfile) + 2 + 1; + char *tmp = (char *) malloc(ssize); + char *s = (char *) malloc(ssize); + + strcpy(tmp, expectfile); + last_dot = strrchr(tmp, '.'); + if (!last_dot) + { + free(tmp); + free(s); + return NULL; + } + *last_dot = '\0'; + snprintf(s, ssize, "%s_%d.%s", tmp, i, last_dot + 1); + free(tmp); + return s; +} + /* * Run a "diff" command and also check that it didn't crash */ @@ -1098,42 +1210,39 @@ run_diff(const char *cmd, const char *filename) * In the true case, the diff is appended to the diffs file. */ static bool -results_differ(const char *testname) +results_differ(const char *testname, const char *resultsfile, const char *default_expectfile) { - const char *expectname; - char resultsfile[MAXPGPATH]; char expectfile[MAXPGPATH]; char diff[MAXPGPATH]; char cmd[MAXPGPATH * 3]; char best_expect_file[MAXPGPATH]; - _resultmap *rm; FILE *difffile; int best_line_count; int i; int l; + const char *platform_expectfile; - /* Check in resultmap if we should be looking at a different file */ - expectname = testname; - for (rm = resultmap; rm != NULL; rm = rm->next) - { - if (strcmp(testname, rm->test) == 0) - { - expectname = rm->resultfile; - break; - } - } + /* + * We can pass either the resultsfile or the expectfile, they should have + * the same type (filename.type) anyway. + */ + platform_expectfile = get_expectfile(testname, resultsfile); - /* Name of test results file */ - snprintf(resultsfile, sizeof(resultsfile), "%s/results/%s.out", - outputdir, testname); + strcpy(expectfile, default_expectfile); + if (platform_expectfile) + { + /* + * Replace everything afer the last slash in expectfile with what the + * platform_expectfile contains. + */ + char *p = strrchr(expectfile, '/'); - /* Name of expected-results file */ - snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out", - inputdir, expectname); + if (p) + strcpy(++p, platform_expectfile); + } /* Name to use for temporary diff file */ - snprintf(diff, sizeof(diff), "%s/results/%s.diff", - outputdir, testname); + snprintf(diff, sizeof(diff), "%s.diff", resultsfile); /* OK, run the diff */ snprintf(cmd, sizeof(cmd), @@ -1153,14 +1262,15 @@ results_differ(const char *testname) for (i = 0; i <= 9; i++) { - snprintf(expectfile, sizeof(expectfile), "%s/expected/%s_%d.out", - inputdir, expectname, i); - if (!file_exists(expectfile)) + char *alt_expectfile; + + alt_expectfile = get_alternative_expectfile(expectfile, i); + if (!file_exists(alt_expectfile)) continue; snprintf(cmd, sizeof(cmd), SYSTEMQUOTE "diff %s \"%s\" \"%s\" > \"%s\"" SYSTEMQUOTE, - basic_diff_opts, expectfile, resultsfile, diff); + basic_diff_opts, alt_expectfile, resultsfile, diff); if (run_diff(cmd, diff) == 0) { @@ -1173,8 +1283,9 @@ results_differ(const char *testname) { /* This diff was a better match than the last one */ best_line_count = l; - strcpy(best_expect_file, expectfile); + strcpy(best_expect_file, alt_expectfile); } + free(alt_expectfile); } /* @@ -1182,14 +1293,11 @@ results_differ(const char *testname) * haven't found a complete match yet. */ - if (strcmp(expectname, testname) != 0) + if (platform_expectfile) { - snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out", - inputdir, testname); - snprintf(cmd, sizeof(cmd), SYSTEMQUOTE "diff %s \"%s\" \"%s\" > \"%s\"" SYSTEMQUOTE, - basic_diff_opts, expectfile, resultsfile, diff); + basic_diff_opts, default_expectfile, resultsfile, diff); if (run_diff(cmd, diff) == 0) { @@ -1203,7 +1311,7 @@ results_differ(const char *testname) { /* This diff was a better match than the last one */ best_line_count = l; - strcpy(best_expect_file, expectfile); + strcpy(best_expect_file, default_expectfile); } } @@ -1230,14 +1338,15 @@ results_differ(const char *testname) } /* - * Wait for specified subprocesses to finish + * Wait for specified subprocesses to finish, and return their exit + * statuses into statuses[] * - * If names isn't NULL, report each subprocess as it finishes + * 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, char **names, int num_tests) +wait_for_tests(PID_TYPE * pids, int *statuses, char **names, int num_tests) { int tests_left; int i; @@ -1254,7 +1363,9 @@ wait_for_tests(PID_TYPE * pids, char **names, int num_tests) PID_TYPE p; #ifndef WIN32 - p = wait(NULL); + int exit_status; + + p = wait(&exit_status); if (p == INVALID_PID) { @@ -1263,6 +1374,7 @@ wait_for_tests(PID_TYPE * pids, char **names, int num_tests) exit_nicely(2); } #else + DWORD exit_status; int r; r = WaitForMultipleObjects(tests_left, active_pids, FALSE, INFINITE); @@ -1282,9 +1394,11 @@ wait_for_tests(PID_TYPE * pids, char **names, int num_tests) 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--; @@ -1298,20 +1412,57 @@ wait_for_tests(PID_TYPE * pids, char **names, int num_tests) #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) +run_schedule(const char *schedule, test_function tfunc) { #define MAX_PARALLEL_TESTS 100 char *tests[MAX_PARALLEL_TESTS]; + _stringlist *resultfiles[MAX_PARALLEL_TESTS]; + _stringlist *expectfiles[MAX_PARALLEL_TESTS]; + _stringlist *tags[MAX_PARALLEL_TESTS]; PID_TYPE pids[MAX_PARALLEL_TESTS]; + 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); + scf = fopen(schedule, "r"); if (!scf) { @@ -1330,6 +1481,15 @@ run_schedule(const char *schedule) 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])) @@ -1393,9 +1553,9 @@ run_schedule(const char *schedule) if (num_tests == 1) { - status(_("test %-20s ... "), tests[0]); - pids[0] = psql_start_test(tests[0]); - wait_for_tests(pids, NULL, 1); + status(_("test %-24s ... "), tests[0]); + 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) @@ -1408,12 +1568,14 @@ run_schedule(const char *schedule) { if (i - oldest >= max_connections) { - wait_for_tests(pids + oldest, tests + oldest, i - oldest); + wait_for_tests(pids + oldest, statuses + oldest, + tests + oldest, i - oldest); oldest = i; } - pids[i] = psql_start_test(tests[i]); + pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]); } - wait_for_tests(pids + oldest, tests + oldest, i - oldest); + wait_for_tests(pids + oldest, statuses + oldest, + tests + oldest, i - oldest); status_end(); } else @@ -1421,19 +1583,49 @@ run_schedule(const char *schedule) status(_("parallel group (%d tests): "), num_tests); for (i = 0; i < num_tests; i++) { - pids[i] = psql_start_test(tests[i]); + pids[i] = (tfunc) (tests[i], &resultfiles[i], &expectfiles[i], &tags[i]); } - wait_for_tests(pids, tests, num_tests); + wait_for_tests(pids, statuses, tests, num_tests); status_end(); } /* Check results for all tests */ for (i = 0; i < num_tests; i++) { + _stringlist *rl, + *el, + *tl; + bool differ = false; + if (num_tests > 1) - status(_(" %-20s ... "), tests[i]); + status(_(" %-24s ... "), tests[i]); - if (results_differ(tests[i])) + /* + * Advance over all three lists simultaneously. + * + * Compare resultfiles[j] with expectfiles[j] always. Tags are + * optional but if there are tags, the tag list has the same + * length as the other two lists. + */ + for (rl = resultfiles[i], el = expectfiles[i], tl = tags[i]; + rl != NULL; /* rl and el have the same length */ + rl = rl->next, el = el->next) + { + 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) + { + printf("%s ", tl->str); + } + differ |= newdiff; + } + + if (differ) { bool ignore = false; _stringlist *sl; @@ -1463,6 +1655,9 @@ run_schedule(const char *schedule) success_count++; } + if (statuses[i] != 0) + log_child_failure(statuses[i]); + status_end(); } } @@ -1474,15 +1669,48 @@ run_schedule(const char *schedule) * Run a single test */ static void -run_single_test(const char *test) +run_single_test(const char *test, test_function tfunc) { PID_TYPE pid; + int exit_status; + _stringlist *resultfiles = NULL; + _stringlist *expectfiles = NULL; + _stringlist *tags = NULL; + _stringlist *rl, + *el, + *tl; + bool differ = false; + + status(_("test %-24s ... "), test); + pid = (tfunc) (test, &resultfiles, &expectfiles, &tags); + wait_for_tests(&pid, &exit_status, NULL, 1); - status(_("test %-20s ... "), test); - pid = psql_start_test(test); - wait_for_tests(&pid, NULL, 1); + /* + * Advance over all three lists simultaneously. + * + * Compare resultfiles[j] with expectfiles[j] always. Tags are optional + * but if there are tags, the tag list has the same length as the other + * two lists. + */ + for (rl = resultfiles, el = expectfiles, tl = tags; + rl != NULL; /* rl and el have the same length */ + rl = rl->next, el = el->next) + { + bool newdiff; + + if (tl) + tl = tl->next; /* tl has the same length as rl and el if it + * exists */ - if (results_differ(test)) + newdiff = results_differ(test, rl->str, el->str); + if (newdiff && tl) + { + printf("%s ", tl->str); + } + differ |= newdiff; + } + + if (differ) { status(_("FAILED")); fail_count++; @@ -1492,6 +1720,10 @@ run_single_test(const char *test) status(_("ok")); success_count++; } + + if (exit_status != 0) + log_child_failure(exit_status); + status_end(); } @@ -1534,6 +1766,105 @@ open_result_files(void) make_directory(file); } +static void +drop_database_if_exists(const char *dbname) +{ + header(_("dropping database \"%s\""), dbname); + psql_command("postgres", "DROP DATABASE IF EXISTS \"%s\"", dbname); +} + +static void +create_database(const char *dbname) +{ + _stringlist *sl; + + /* + * We use template0 so that any installation-local cruft in template1 will + * not mess up the tests. + */ + header(_("creating database \"%s\""), dbname); + if (encoding) + psql_command("postgres", "CREATE DATABASE \"%s\" TEMPLATE=template0 ENCODING='%s'%s", dbname, encoding, + (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : ""); + else + psql_command("postgres", "CREATE DATABASE \"%s\" TEMPLATE=template0%s", dbname, + (nolocale) ? " LC_COLLATE='C' LC_CTYPE='C'" : ""); + psql_command(dbname, + "ALTER DATABASE \"%s\" SET lc_messages TO 'C';" + "ALTER DATABASE \"%s\" SET lc_monetary TO 'C';" + "ALTER DATABASE \"%s\" SET lc_numeric TO 'C';" + "ALTER DATABASE \"%s\" SET lc_time TO 'C';" + "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';", + 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); + } + + /* + * Install any requested extensions. We use CREATE IF NOT EXISTS so that + * this will work whether or not the extension is preinstalled. + */ + for (sl = loadextension; sl != NULL; sl = sl->next) + { + header(_("installing %s"), sl->str); + psql_command(dbname, "CREATE EXTENSION IF NOT EXISTS \"%s\"", sl->str); + } +} + +static void +drop_role_if_exists(const char *rolename) +{ + header(_("dropping role \"%s\""), rolename); + psql_command("postgres", "DROP ROLE IF EXISTS \"%s\"", rolename); +} + +static void +create_role(const char *rolename, const _stringlist * granted_dbs) +{ + header(_("creating role \"%s\""), rolename); + psql_command("postgres", "CREATE ROLE \"%s\" WITH LOGIN", rolename); + for (; granted_dbs != NULL; granted_dbs = granted_dbs->next) + { + psql_command("postgres", "GRANT ALL ON DATABASE \"%s\" TO \"%s\"", + granted_dbs->str, rolename); + } +} + +static char * +make_absolute_path(const char *in) +{ + char *result; + + if (is_absolute_path(in)) + result = strdup(in); + else + { + static char cwdbuf[MAXPGPATH]; + + if (!cwdbuf[0]) + { + if (!getcwd(cwdbuf, sizeof(cwdbuf))) + { + fprintf(stderr, _("could not get current working directory: %s\n"), strerror(errno)); + exit_nicely(2); + } + } + + result = malloc(strlen(cwdbuf) + strlen(in) + 2); + sprintf(result, "%s/%s", cwdbuf, in); + } + + canonicalize_path(result); + return result; +} + static void help(void) { @@ -1547,19 +1878,26 @@ help(void) printf(_(" --inputdir=DIR take input files from DIR (default \".\")\n")); printf(_(" --load-language=lang load the named language before running the\n")); printf(_(" tests; can appear multiple times\n")); + printf(_(" --load-extension=ext load the named extension before running the\n")); + printf(_(" tests; can appear multiple times\n")); + printf(_(" --create-role=ROLE create the specified role before testing\n")); printf(_(" --max-connections=N maximum number of concurrent connections\n")); printf(_(" (default is 0 meaning unlimited)\n")); - printf(_(" --multibyte=ENCODING use ENCODING as the multibyte encoding\n")); + printf(_(" --encoding=ENCODING use ENCODING as the encoding\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(_(" --srcdir=DIR absolute path to source directory (for VPATH builds)\n")); + printf(_(" --dlpath=DIR look for dynamic libraries in DIR\n")); printf(_(" --temp-install=DIR create a temporary installation in DIR\n")); - printf(_(" --no-locale use C locale\n")); + printf(_(" --use-existing use an existing installation\n")); + printf(_(" --launcher=CMD use CMD as launcher of psql\n")); printf(_("\n")); printf(_("Options for \"temp-install\" mode:\n")); + printf(_(" --no-locale use C locale\n")); printf(_(" --top-builddir=DIR (relative) path to top level build directory\n")); - printf(_(" --temp-port=PORT port number to start temp postmaster on\n")); + printf(_(" --port=PORT start postmaster on PORT\n")); + printf(_(" --temp-config=PATH append contents of PATH to temporary config\n")); + printf(_(" --extra-install=DIR additional directory to install (e.g., contrib\n")); printf(_("\n")); printf(_("Options for using an existing installation:\n")); printf(_(" --host=HOST use postmaster running on HOST\n")); @@ -1574,13 +1912,14 @@ help(void) } int -main(int argc, char *argv[]) +regression_main(int argc, char *argv[], init_function ifunc, test_function tfunc) { _stringlist *sl; int c; int i; int option_index; char buf[MAXPGPATH * 4]; + char buf2[MAXPGPATH * 4]; static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, @@ -1590,29 +1929,40 @@ main(int argc, char *argv[]) {"inputdir", required_argument, NULL, 3}, {"load-language", required_argument, NULL, 4}, {"max-connections", required_argument, NULL, 5}, - {"multibyte", required_argument, NULL, 6}, + {"encoding", required_argument, NULL, 6}, {"outputdir", required_argument, NULL, 7}, {"schedule", required_argument, NULL, 8}, {"temp-install", required_argument, NULL, 9}, {"no-locale", no_argument, NULL, 10}, {"top-builddir", required_argument, NULL, 11}, - {"temp-port", required_argument, NULL, 12}, {"host", required_argument, NULL, 13}, {"port", required_argument, NULL, 14}, {"user", required_argument, NULL, 15}, {"psqldir", required_argument, NULL, 16}, - {"srcdir", required_argument, NULL, 17}, + {"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}, {NULL, 0, NULL, 0} }; progname = get_progname(argv[0]); - set_pglocale_pgservice(argv[0], "pg_regress"); + set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_regress")); #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(); + while ((c = getopt_long(argc, argv, "hV", long_options, &option_index)) != -1) { switch (c) @@ -1621,10 +1971,16 @@ main(int argc, char *argv[]) help(); exit_nicely(0); case 'V': - printf("pg_regress (PostgreSQL %s)\n", PG_VERSION); + puts("pg_regress (PostgreSQL) " PG_VERSION); exit_nicely(0); case 1: - dbname = strdup(optarg); + + /* + * If a default database was specified, we need to remove it + * before we add the specified one. + */ + free_stringlist(&dblist); + split_to_stringlist(strdup(optarg), ", ", &dblist); break; case 2: debug = true; @@ -1648,22 +2004,7 @@ main(int argc, char *argv[]) add_stringlist_item(&schedulelist, optarg); break; case 9: - /* temp_install must be absolute path */ - if (is_absolute_path(optarg)) - temp_install = strdup(optarg); - else - { - char cwdbuf[MAXPGPATH]; - - if (!getcwd(cwdbuf, sizeof(cwdbuf))) - { - fprintf(stderr, _("could not get current working directory: %s\n"), strerror(errno)); - exit_nicely(2); - } - temp_install = malloc(strlen(cwdbuf) + strlen(optarg) + 2); - sprintf(temp_install, "%s/%s", cwdbuf, optarg); - } - canonicalize_path(temp_install); + temp_install = make_absolute_path(optarg); break; case 10: nolocale = true; @@ -1671,20 +2012,12 @@ main(int argc, char *argv[]) case 11: top_builddir = strdup(optarg); break; - case 12: - { - int p = atoi(optarg); - - /* Since Makefile isn't very bright, check port range */ - if (p >= 1024 && p <= 65535) - temp_port = p; - } - break; case 13: hostname = strdup(optarg); break; case 14: port = atoi(optarg); + port_specified_by_user = true; break; case 15: user = strdup(optarg); @@ -1695,7 +2028,25 @@ main(int argc, char *argv[]) psqldir = strdup(optarg); break; case 17: - srcdir = strdup(optarg); + dlpath = strdup(optarg); + break; + case 18: + split_to_stringlist(strdup(optarg), ", ", &extraroles); + break; + case 19: + temp_config = strdup(optarg); + break; + case 20: + use_existing = true; + break; + case 21: + launcher = strdup(optarg); + break; + case 22: + add_stringlist_item(&loadextension, optarg); + break; + case 23: + add_stringlist_item(&extra_install, optarg); break; default: /* getopt_long already emitted a complaint */ @@ -1714,8 +2065,18 @@ main(int argc, char *argv[]) optind++; } - if (temp_install) - port = temp_port; + if (temp_install && !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. + */ + port = 0xC000 | (PG_VERSION_NUM & 0x3FFF); + + inputdir = make_absolute_path(inputdir); + outputdir = make_absolute_path(outputdir); + dlpath = make_absolute_path(dlpath); /* * Initialization @@ -1730,6 +2091,9 @@ main(int argc, char *argv[]) if (temp_install) { + FILE *pg_conf; + _stringlist *sl; + /* * Prepare the temp installation */ @@ -1758,12 +2122,12 @@ main(int argc, char *argv[]) /* "make install" */ #ifndef WIN32_ONLY_COMPILER snprintf(buf, sizeof(buf), - SYSTEMQUOTE "\"%s\" -C \"%s\" DESTDIR=\"%s/install\" install with_perl=no with_python=no > \"%s/log/install.log\" 2>&1" SYSTEMQUOTE, + SYSTEMQUOTE "\"%s\" -C \"%s\" DESTDIR=\"%s/install\" install > \"%s/log/install.log\" 2>&1" SYSTEMQUOTE, makeprog, top_builddir, temp_install, outputdir); #else - snprintf(buf, sizeof(buf), - SYSTEMQUOTE "perl \"%s/src/tools/msvc/install.pl\" \"%s/install\" >\"%s/log/install.log\" 2>&1" SYSTEMQUOTE, - top_builddir, temp_install, outputdir); + snprintf(buf, sizeof(buf), + SYSTEMQUOTE "perl \"%s/src/tools/msvc/install.pl\" \"%s/install\" >\"%s/log/install.log\" 2>&1" SYSTEMQUOTE, + top_builddir, temp_install, outputdir); #endif if (system(buf)) { @@ -1771,6 +2135,24 @@ main(int argc, char *argv[]) exit_nicely(2); } + for (sl = extra_install; sl != NULL; sl = sl->next) + { +#ifndef WIN32_ONLY_COMPILER + snprintf(buf, sizeof(buf), + SYSTEMQUOTE "\"%s\" -C \"%s/%s\" DESTDIR=\"%s/install\" install >> \"%s/log/install.log\" 2>&1" SYSTEMQUOTE, + makeprog, top_builddir, sl->str, temp_install, outputdir); +#else + fprintf(stderr, _("\n%s: --extra-install option not supported on this platform\n", progname)); + exit_nicely(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_nicely(2); + } + } + /* initdb */ header(_("initializing database system")); snprintf(buf, sizeof(buf), @@ -1785,6 +2167,73 @@ main(int argc, char *argv[]) exit_nicely(2); } + /* + * Adjust the default postgresql.conf as needed for regression + * testing. The user can specify a file to be appended; in any case we + * 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_install); + 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_nicely(2); + } + fputs("\n# Configuration added by pg_regress\n\n", pg_conf); + fputs("max_prepared_transactions = 2\n", pg_conf); + + if (temp_config != NULL) + { + FILE *extra_conf; + char line_buf[1024]; + + extra_conf = fopen(temp_config, "r"); + if (extra_conf == NULL) + { + fprintf(stderr, _("\n%s: could not open \"%s\" to read extra config: %s\n"), progname, temp_config, strerror(errno)); + exit_nicely(2); + } + while (fgets(line_buf, sizeof(line_buf), extra_conf) != NULL) + fputs(line_buf, pg_conf); + fclose(extra_conf); + } + + fclose(pg_conf); + + /* + * Check if there is a postmaster running already. + */ + snprintf(buf2, sizeof(buf2), + SYSTEMQUOTE "\"%s/psql\" -X postgres <%s 2>%s" SYSTEMQUOTE, + 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_nicely(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 */ @@ -1808,13 +2257,10 @@ main(int argc, char *argv[]) * second or so, but Cygwin is reportedly *much* slower). Don't wait * forever, however. */ - snprintf(buf, sizeof(buf), - SYSTEMQUOTE "\"%s/psql\" -X postgres <%s 2>%s" SYSTEMQUOTE, - bindir, DEVNULL, DEVNULL); for (i = 0; i < 60; i++) { /* Done if psql succeeds */ - if (system(buf) == 0) + if (system(buf2) == 0) break; /* @@ -1858,51 +2304,39 @@ main(int argc, char *argv[]) postmaster_running = true; +#ifdef WIN64 +/* need a series of two casts to convert HANDLE without compiler warning */ +#define ULONGPID(x) (unsigned long) (unsigned long long) (x) +#else +#define ULONGPID(x) (unsigned long) (x) +#endif printf(_("running on port %d with pid %lu\n"), - temp_port, (unsigned long) postmaster_pid); + port, ULONGPID(postmaster_pid)); } else { /* * Using an existing installation, so may need to get rid of - * pre-existing database. + * pre-existing database(s) and role(s) */ - header(_("dropping database \"%s\""), dbname); - psql_command("postgres", "DROP DATABASE IF EXISTS \"%s\"", dbname); + 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 - * - * We use template0 so that any installation-local cruft in template1 will - * not mess up the tests. - */ - header(_("creating database \"%s\""), dbname); - if (encoding) - psql_command("postgres", - "CREATE DATABASE \"%s\" TEMPLATE=template0 ENCODING='%s'", - dbname, encoding); - else - /* use installation default */ - psql_command("postgres", - "CREATE DATABASE \"%s\" TEMPLATE=template0", - dbname); - - psql_command(dbname, - "ALTER DATABASE \"%s\" SET lc_messages TO 'C';" - "ALTER DATABASE \"%s\" SET lc_monetary TO 'C';" - "ALTER DATABASE \"%s\" SET lc_numeric TO 'C';" - "ALTER DATABASE \"%s\" SET lc_time TO 'C';" - "ALTER DATABASE \"%s\" SET timezone_abbreviations TO 'Default';", - dbname, dbname, dbname, dbname, dbname); - - /* - * Install any requested PL languages + * Create the test database(s) and role(s) */ - for (sl = loadlanguage; sl != NULL; sl = sl->next) + if (!use_existing) { - header(_("installing %s"), sl->str); - psql_command(dbname, "CREATE LANGUAGE \"%s\"", sl->str); + for (sl = dblist; sl; sl = sl->next) + create_database(sl->str); + for (sl = extraroles; sl; sl = sl->next) + create_role(sl->str, dblist); } /* @@ -1912,12 +2346,12 @@ main(int argc, char *argv[]) for (sl = schedulelist; sl != NULL; sl = sl->next) { - run_schedule(sl->str); + run_schedule(sl->str, tfunc); } for (sl = extra_tests; sl != NULL; sl = sl->next) { - run_single_test(sl->str); + run_single_test(sl->str, tfunc); } /*