/*-------------------------------------------------------------------------
*
* pg_regress --- regression test driver
*
* This is a C implementation of the previous shell script for running
* the regression tests, and should be mostly compatible with it.
* Initial author of C translation: Magnus Hagander
*
* This code is released under the terms of the PostgreSQL License.
*
* Portions Copyright (c) 1996-2011, 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 <sys/stat.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/time.h>
#endif
#include "getopt_long.h"
#include "pg_config_paths.h"
#define DATANODE 1
#define COORD 2
#define GTM 3
#define SHELL 4
#ifdef PGXC
/*
* In Postgres-XC, a regression test check is run on 2 Coordinators
* and 2 Datanodes. Coordinator 1 is considered as being the default
* used in pg_regress.
* External connections are made to Coordinator 1.
* All connections to remote nodes are made from Coordinator 1.
* Here is the list of nodes identified with a unique ID.
*/
typedef enum
{
PGXC_COORD_1,
PGXC_COORD_2,
PGXC_DATANODE_1,
PGXC_DATANODE_2,
PGXC_GTM
} PGXCNodeTypeNum;
#endif
pgxc_node_info myinfo;
/* for resultmap we need a list of pairs of strings */
typedef struct _resultmap
{
char* test;
char* type;
char* resultfile;
struct _resultmap* next;
} _resultmap;
/*
* Values obtained from 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.
*/
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
/*
* 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
#define LOCAL_IP_LEN 20
#define REGR_MAX_NUM_OF_LINE_BUFF 12
#define REGR_MAX_PARALLEL_TESTS 200
#define REGR_MAX_SCH_LINE_SIZE (32 * 1024)
/* Maximum length for the time string */
#define MAX_REG_TIME_STR_LEN 25
/* Maximum token length */
#define MAX_REGR_TOKEN_LEN 25
double g_dTotalTime;
double g_dGroupTotalTime;
char g_acTestStartTime[MAX_REG_TIME_STR_LEN];
char g_acDataPath[MAXPGPATH];
#define REGR_CHR_EOS '\0'
#define REGR_MCR_SIZE_NIL 0
#define REGR_MCR_SIZE_1B 1
#define REGR_MCR_SIZE_1KB 1024
#define REGR_MCR_SIZE_1MB 1048576
#define REGR_MCR_SIZE_1GB 1073741824
char** g_ppcBuf = NULL;
unsigned int g_uiCurLineBufIdx = 0;
unsigned int g_uiTotalBuf = 0;
/* Maximum line length in regress.conf file */
#define MAX_LINE_LEN (1024)
/* Maximum length for the name of a configuration item in
* regress.conf file */
#define MAX_CONF_ITEM_NAME_LEN (128)
/* Current number of configuration items in regress.conf file */
#define MAX_REG_CONF_ITEMS (4)
#define REGR_DELAY_FOR_PERF_COLLECTION (100000L)
#define REGR_DELAY_FOR_PID_FILE_OPEN (1000000L)
#define REGR_NUM_OF_RETRY_FOR_PID_FILE_OPEN (15)
#define REGR_PID_WAIT_FOR_ANY_CHILD (-1)
#define NO_CHILD_PROC_HAS_CHANGED_STATE (0)
#define REGR_SIZE_OF_INDEX_ELEM (sizeof(unsigned int))
/* Array index definition for items in aacRegConfItemName */
#define REGR_TUPONLY_ID 0
#define REGR_COL_SEP_ID 1
#define REGR_PERF_DATA_LOGGING_ID 2
#define REGR_DIAG_COLLECT_ID 3
#define REGR_SUCCESS 0
#define REGR_ERRCODE_MALLOC_FAILURE 1
#define REGR_ERRCODE_FOPEN_FAILURE 2
#define REGR_ERRCODE_ENV_FETCH_FAILED 3
#define REGR_ERRCODE_BUFF_NOT_ENOUGH 4
#define REGR_ERRCODE_RESRC_STAT_FETCH_FAILED 5
#define REGR_ERRCODE_SYNTAX_ERR_IN_SCH 6
#define REGR_EOF_REACHED 7
#define REGR_ERRCODE_MAX_NUM_OF_LINE_BUFF 8
/* The number by which the memory block for replacement patterns gets expanded.
* It is also indicating the initial number of slots that is getting allocated*/
#define REGR_NUM_OF_REPLC_ITEMS_CHUNK 25
/* This is for the rough estimation of the size of the memory that need to be
* allocated for the storage of the replacement pattern strings.
* This shows the average size of the replacement pattern string */
#define REGR_AVG_REPLC_PATT_LEN 64
/* This is for the rough estimation of the size of the memory that need to be
* allocated for the storage of the replacement pattern strings.
* This shows the average size of the replacement pattern string */
#define REGR_AVG_REPLC_PATT_VAL_LEN 512
/* INDICATOR for the start of the replacement pattern string */
#define REGR_REPLC_PATT_START_CHAR '@'
/* INDICATOR for the end of the replacement pattern string */
#define REGR_REPLC_PATT_END_CHAR '@'
#define REGR_FREE(pvAddr) free((pvAddr)); (pvAddr) = NULL
#define REGR_START_TIMER \
{\
if (g_bEnablePerfDataPrint)\
{\
time_t timer; \
struct tm* pstTmInfo = NULL; \
(void)gettimeofday(&g_stRegrStartTime, NULL); \
(void)time(&timer); \
pstTmInfo = localtime(&timer);\
if (NULL != pstTmInfo)\
(void)strftime(g_acTestStartTime, MAX_REG_TIME_STR_LEN,\
"%Y-%m-%d %H:%M:%S", pstTmInfo);\
}\
}
#define REGR_STOP_TIMER \
{\
if (g_bEnablePerfDataPrint)\
{\
(void)gettimeofday(&g_stRegrStopTime, NULL);\
regrGetElapsedTime(); \
}\
}
#define REGR_PRINT_ELAPSED_TIME \
{\
if (g_bEnablePerfDataPrint)\
{\
status(_("TIME START: [%s]. TIME TAKEN: [%9.3lf] %%UCPU: [%5.2lf] "\
"%%SCPU: [%5.2lf] %%MEM: [%5.2lf] MEM: [%-9s]\n"),\
g_acTestStartTime, g_dTotalTime, \
g_stResourceUsageDetails.dUAvgcpuUsage,\
g_stResourceUsageDetails.dSAvgcpuUsage,\
g_stResourceUsageDetails.dAvgMemUsagePct,\
g_stResourceUsageDetails.acMemUsage);\
status_end();\
}\
}
typedef struct tagREGR_AVG_RSRCE_USAGE_STRU
{
double dUAvgcpuUsage;
double dSAvgcpuUsage;
double dAvgMemUsage;
double dAvgMemUsagePct;
long unsigned int ulCount;
char acMemUsage[MAX_REGR_TOKEN_LEN];
} REGR_AVG_RSRCE_USAGE_STRU;
typedef struct tagREGR_RESRC_STAT_STRU
{
long unsigned int ulUtimeTicks;
long int lCutimeTicks;
long unsigned int ulStimeTicks;
long int lCstimeTicks;
long unsigned int vsize;
long unsigned int rss; /* virtual memory size in bytes */
long unsigned int ulCpuTotalTime; /* Resident Set Size in bytes*/
double dMemPct; /* %MEM */
} REGR_RESRC_STAT_STRU;
/* Stores the start time of a test file execution */
#define REG_MAX_NUM (50)
double g_dOneGroupTotalTime;
double g_dOneRegrTotalTime[REG_MAX_NUM];
char g_acOneRegrTestStartTime[REG_MAX_NUM][MAX_REG_TIME_STR_LEN];
static struct timeval g_stRegrStartTimeTmp[REG_MAX_NUM];
/* Stores the end time of a test file execution */
static struct timeval g_stRegrStopTimeTmp[REG_MAX_NUM];
#define REGR_START_TIMER_TEMP(i) \
{\
if (g_bEnablePerfDataPrint)\
{\
time_t timer; \
struct tm* pstTmInfo = NULL; \
(void)gettimeofday(&g_stRegrStartTimeTmp[i], NULL); \
(void)time(&timer); \
pstTmInfo = localtime(&timer);\
if (NULL != pstTmInfo)\
(void)strftime(g_acOneRegrTestStartTime[i], MAX_REG_TIME_STR_LEN,\
"%Y-%m-%d %H:%M:%S", pstTmInfo);\
}\
}
#define REGR_STOP_TIMER_TEMP(i) \
{\
if (g_bEnablePerfDataPrint)\
{\
(void)gettimeofday(&g_stRegrStopTimeTmp[i], NULL);\
regrGetOneRegrElapsedTime(i); \
}\
}
#define REGR_PRINT_ELAPSED_TIME_TEMP(i) \
{\
if (g_bEnablePerfDataPrint)\
{\
status(_("TIME TAKEN: [%9.3lf]"),\
g_dOneRegrTotalTime[i]);\
status_end();\
}\
}
#define REGR_PRINT_ONEGROUP_ELAPSED_TIME \
{\
if (g_bEnablePerfDataPrint)\
{\
struct timeval stRegrEndTime;\
(void)gettimeofday(&stRegrEndTime, NULL); \
g_dOneGroupTotalTime = (stRegrEndTime.tv_sec - g_stRegrStartTimeTmp[0].tv_sec) +\
(stRegrEndTime.tv_usec - g_stRegrStartTimeTmp[0].tv_usec)\
* 0.000001;\
g_dGroupTotalTime = g_dGroupTotalTime + g_dOneGroupTotalTime;\
/*status(_("TIME TAKEN: [%9.3lf] MEM: [%-9s]"),\
g_dOneGroupTotalTime,\
g_stResourceUsageDetails.acMemUsage);\ */\
g_dOneGroupTotalTime = 0;\
/*status_end();*/\
}\
}
static void
regrGetOneRegrElapsedTime(int i)
{
g_dOneRegrTotalTime[i] = (g_stRegrStopTimeTmp[i].tv_sec - g_stRegrStartTimeTmp[i].tv_sec) +
(g_stRegrStopTimeTmp[i].tv_usec - g_stRegrStartTimeTmp[i].tv_usec)
* 0.000001;
}
REGR_RESRC_STAT_STRU g_stPrevCpuUsage;
REGR_RESRC_STAT_STRU g_stCurrCpuUsage;
REGR_AVG_RSRCE_USAGE_STRU g_stResourceUsageDetails;
/* Names of the configuration items in regress.conf file */
char aacRegConfItemName[][MAX_CONF_ITEM_NAME_LEN]
= {"column_name_present",
"column_separator",
"performance_data_printing",
"diagnostic_collect_on_fail"};
/* options settable from command line */
_stringlist* dblist = NULL;
bool debug = false;
char* inputdir = ".";
char* outputdir = ".";
char* psqldir = PGBINDIR;
char* launcher = NULL;
char* loginuser = 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* pcRegConfFile = NULL;
static char* temp_install = NULL;
static char* temp_config = NULL;
static char* top_builddir = NULL;
static bool nolocale = false;
static bool use_existing = false;
static char* hostname = NULL;
static int port = -1;
static bool comm_tcp_mode = true;
static char* hdfshostname = "nohostname";
static char* hdfscfgpath = "noconfigpath";
static char* hdfsport = "noport";
static char* hdfsstoreplus="nohdfsstoreplus";
static int dop = 1;
static bool port_specified_by_user = false;
static char* dlpath = PKGLIBDIR;
static char* user = NULL;
static _stringlist* extraroles = NULL;
static _stringlist* extra_install = NULL;
static FILE* start_script;
static FILE* stop_script;
/* internal variables */
static const char* progname;
static char* logfilename;
static FILE* logfile;
char* difffilename;
static bool clean = true;
static _resultmap* resultmap = NULL;
static bool postmaster_running = false;
static bool standby_defined = false;
static int success_count = 0;
static int fail_count = 0;
static int fail_ignore_count = 0;
/* TRUE if diagnostic data need to be collected on test failure */
static bool g_bEnableDiagCollection = false;
/* TRUE if performance related data need to be printed,asPer the config value */
static bool g_bEnablePerfDataPrint = false;
static PID_TYPE g_iPostmasterPid = INVALID_PID;
/* Stores the start time of a test file execution */
static struct timeval g_stRegrStartTime;
/* Stores the end time of a test file execution */
static struct timeval g_stRegrStopTime;
/* To store the values of the regress.conf values */
REGR_CONF_ITEMS_STRU g_stRegrConfItems;
/* Module-global structure instance for the storing the details of the
* replacement pattern strings */
static REGR_REPLACE_PATTERNS_STRU g_stRegrReplcPatt;
/* In default, we do not change initial password */
static bool change_password = false;
static bool directory_exists(const char *dir);
static void make_directory(const char *dir);
static void convertSourcefilesIn(char*, char*, char*, char*);
static void loadRegressConf(char* pcRegConfFile);
static void processTuplesOnlyConfItem(char* pcConfigValue);
static void processColSepConfItem(const char* pcConfigValue);
static int regressStrncasecmp(const char *s1, const char *s2, int maxLenToCmp);
static void processPerfDataLoggingConf(const char* pcConfigValue);
static bool regrValidateBoolConfigVal(const char* pcConfigValue, int iConfId);
static void regrGetElapsedTime(void);
static int regrGetResrcUsage(const pid_t pid, REGR_RESRC_STAT_STRU* result);
static void regrCalcCpuUsagePct(const REGR_RESRC_STAT_STRU* pstCurrUsage,
const REGR_RESRC_STAT_STRU* pstPrevUsage,
double* pdUcpuUsagePct,
double* pdScpuUsagePct);
static void regrConvertSizeInBytesToReadableForm(unsigned long int ulValue,
char* pcBuf,
unsigned int uiBufSize);
static void kill_node(int i, int type);
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
#ifdef PGXC
static void
psql_command_node(const char* database, int i, int type, const char* query, ...)
/* This extension allows gcc to check the format string for consistency with
the supplied arguments. */
__attribute__((format(PG_PRINTF_ATTRIBUTE, 4, 5)));
#endif
static char* get_node_info_name(int i, int type, bool is_node_name);
/*
* allow core files if possible.
*/
#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
static void
unlimit_core_size(void)
{
struct rlimit lim;
(void)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;
(void)setrlimit(RLIMIT_CORE, &lim);
}
}
#endif
/*
* Converts the size (represented in bytes), to human readable form
* (in KB, MB or in GB) and the result is placed in the buffer.
*/
static void regrConvertSizeInBytesToReadableForm(unsigned long int ulValue,
char* pcBuf,
unsigned int uiBufSize)
{
/* Representing the passed value as such because it is less than 1 KB */
if (ulValue < REGR_MCR_SIZE_1KB)
(void)snprintf(pcBuf, uiBufSize, "%lu B", ulValue);
else if ((ulValue >= REGR_MCR_SIZE_1KB) && (ulValue < REGR_MCR_SIZE_1MB))
{
double dSize = (ulValue / (double)REGR_MCR_SIZE_1KB);
/* Representing the value in KB */
(void)snprintf(pcBuf, uiBufSize, "%-7.2f KB", dSize);
}
else if ((ulValue >= REGR_MCR_SIZE_1MB) && (ulValue < REGR_MCR_SIZE_1GB))
{
double dSize = (ulValue / (double)REGR_MCR_SIZE_1MB);
/* Representing the value in MB */
(void)snprintf(pcBuf, uiBufSize, "%-7.2f MB", dSize);
}
else if (ulValue >= REGR_MCR_SIZE_1GB)
{
double dSize = (ulValue / (double)REGR_MCR_SIZE_1GB);
/* Representing the value in GB */
(void)snprintf(pcBuf, uiBufSize, "%-7.2f GB", dSize);
}
}
#define TwoPhaseTxntTestDirInput "2PCTxnt/input"
#define TwoPhaseTxntTestDirOutput "2PCTxnt/output"
/*
* Gets the process id of the GAUSS Server process and the result will be
* stored in g_iPostmasterPid
*/
static int regrGetServPid(char* pcBuff,
unsigned int uiBuffLen,
unsigned int* puiPidChanged)
{
PID_TYPE iPid = INVALID_PID;
int iIter = REGR_NUM_OF_RETRY_FOR_PID_FILE_OPEN;
FILE* fpPMPid = NULL;
const char* data_folder = get_node_info_name(0, COORD, false);
char cwd[MAXPGPATH] = {0};
if (!getcwd(cwd, MAXPGPATH))
{
fprintf(stderr,
_("\n Get current dir fail.\n"));
return -1;
}
if (strlen(cwd) +strlen("/tmp_check/")+strlen(data_folder)+strlen("/postmaster.pid") >= uiBuffLen)
{
fprintf(stderr, _("\n Buffer space not enough for holding the path of "
"the \'postmaster.pid\' file. Buffer Len: %u. Buffer "
"length needed: %lu.\n"),
uiBuffLen,
(strlen(cwd) + strlen("/")+strlen("tmp_check/")+strlen(data_folder)+ strlen("/postmaster.pid") + 1));
return REGR_ERRCODE_BUFF_NOT_ENOUGH;
}
(void)memset(pcBuff, '\0', uiBuffLen);
/*(void)strncpy(pcBuff, pcDataPath, uiBuffLen - 1);
(void)strncat(pcBuff, "/postmaster.pid",
(uiBuffLen - strlen(pcDataPath) - 1));*/
(void)snprintf(pcBuff, uiBuffLen,
SYSTEMQUOTE "%s/tmp_check/%s/postmaster.pid" SYSTEMQUOTE,
cwd, data_folder);
do
{
fpPMPid = fopen(pcBuff, "r");
if (NULL != fpPMPid )
break;
iIter--;
pg_usleep(REGR_DELAY_FOR_PID_FILE_OPEN);
} while (iIter > 0);
if (NULL == fpPMPid)
{
fprintf(stderr,
_("\n Could not open: [%s] for getting Server PID. \n"),
pcBuff);
return REGR_ERRCODE_FOPEN_FAILURE;
}
iPid = g_iPostmasterPid;
g_iPostmasterPid = INVALID_PID;
fscanf(fpPMPid, "%d", &g_iPostmasterPid);
fclose(fpPMPid);
if ((INVALID_PID != iPid) && (g_iPostmasterPid != iPid))
{
*puiPidChanged = true;
}
return REGR_SUCCESS;
}
/*
* read /proc data into the passed REGR_RESRC_STAT_STRU
* returns 0 on success, -1 on error
*/
static int regrGetResrcUsage(const pid_t pid, REGR_RESRC_STAT_STRU* result)
{
char pid_s[20]; /* convert pid to string */
int i;
int iRet = 0;
long int slRss;
unsigned long int aulCpuTime[10] = {0};
unsigned long int ulTotalMemory = 0;
FILE *fpstat = NULL;
FILE *fcstat = NULL;
FILE *fpMemInfo = NULL;
char stat_filepath[30] = "/proc/";
(void)snprintf(pid_s, sizeof(pid_s), "%d", pid);
strncat(stat_filepath, pid_s,
(sizeof(stat_filepath) - strlen(stat_filepath) - 1));
strncat(stat_filepath, "/stat",
(sizeof(stat_filepath) - strlen(stat_filepath) - 1));
fpstat = fopen(stat_filepath, "r");
if (fpstat == NULL)
return -1;
fcstat = fopen("/proc/stat", "r");
if (fcstat == NULL)
{
iRet = -2;
goto LB_ERR_LVL_1;
}
fpMemInfo = fopen("/proc/meminfo", "r");
if (NULL == fpMemInfo)
{
iRet = -3;
goto LB_ERR_LVL_2;
}
(void)memset(result, 0, sizeof(REGR_RESRC_STAT_STRU));
/* Read values from /proc/pid/stat */
if (fscanf(fpstat, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu"
"%lu %ld %ld %*d %*d %*d %*d %*u %lu %ld",
&result->ulUtimeTicks,
&result->ulStimeTicks,
&result->lCutimeTicks,
&result->lCstimeTicks,
&result->vsize,
&slRss) == EOF)
{
iRet = -4;
goto LB_ERR_LVL_3;
}
/* slRss will show the total number of pages actually in memory */
result->rss = slRss * getpagesize();
/* Read and calculate 'cpu total time' from /proc/stat */
if (fscanf(fcstat, "%*s %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
&aulCpuTime[0],
&aulCpuTime[1],
&aulCpuTime[2],
&aulCpuTime[3],
&aulCpuTime[4],
&aulCpuTime[5],
&aulCpuTime[6],
&aulCpuTime[7],
&aulCpuTime[8],
&aulCpuTime[9]) == EOF)
{
iRet = -5;
goto LB_ERR_LVL_3;
}
for(i = 0; i < 10; i++)
result->ulCpuTotalTime += aulCpuTime[i];
/* Get the total memory (in kb) from /proc/meminfo file */
if (fscanf(fpMemInfo, "%*s %lu", &ulTotalMemory) == EOF)
{
iRet = -6;
goto LB_ERR_LVL_3;
}
/* Converting kb value into bytes */
ulTotalMemory *= 1024;
result->dMemPct = (result->rss * 100) / ulTotalMemory;
/*printf("\n Rss (size of pages in memory): [%lu bytes]. Total Memory:"
" [%lu bytes]. Mem Pct: [%lf]...\n", result->rss, ulTotalMemory, result->dMemPct);*/
iRet = 0;
LB_ERR_LVL_3:
fclose(fpMemInfo);
LB_ERR_LVL_2:
fclose(fcstat);
LB_ERR_LVL_1:
fclose(fpstat);
return iRet;
}
/* Calculates the elapsed CPU usage between 2 measuring points. in percent */
static void regrCalcCpuUsagePct(const REGR_RESRC_STAT_STRU* pstCurrUsage,
const REGR_RESRC_STAT_STRU* pstPrevUsage,
double* pdUcpuUsagePct,
double* pdScpuUsagePct)
{
const long unsigned int total_time_diff
= pstCurrUsage->ulCpuTotalTime - pstPrevUsage->ulCpuTotalTime;
if (0 != total_time_diff)
{
*pdUcpuUsagePct
= 100 * (((pstCurrUsage->ulUtimeTicks + pstCurrUsage->lCutimeTicks)
- (pstPrevUsage->ulUtimeTicks + pstPrevUsage->lCutimeTicks))
/ (double) total_time_diff);
*pdScpuUsagePct
= 100 * ((((pstCurrUsage->ulStimeTicks + pstCurrUsage->lCstimeTicks)
- (pstPrevUsage->ulStimeTicks + pstPrevUsage->lCstimeTicks)))
/ (double) total_time_diff);
}
else
{
*pdUcpuUsagePct = 0.0;
*pdScpuUsagePct = 0.0;
}
}
/* Get the CPU and Memory usage details from /proc/[pid]/stat file. If the
* server pid has become invalid (may be because of the server restart by the
* recovery framework), then the new server pid is fetched again and with this
* new value, we try to get CPU usage details. If the server PID has changed
* then puiPidChanged will be set to TRUE.
* In this case, all the readings taken for the old server is obsolete.
* So, the readings which are taken till that point, will be discarded. Thus
* the readings will not be accurate, for the tests which involves recovery.
*/
static int regrGetRsrcUsageWithRepeatOnFail(
REGR_RESRC_STAT_STRU* pstResrcUsageStat,
unsigned int* puiPidChanged)
{
int iRet = REGR_SUCCESS;
int iRetServPid = REGR_SUCCESS;
unsigned int uiExit = false;
do
{
iRet = regrGetResrcUsage(g_iPostmasterPid, pstResrcUsageStat);
if (REGR_SUCCESS == iRet)
break;
/* Could not get the CPU usage for the current PID value.
* Server may have been restarted. So, get the updated ServerPID value*/
if ((true == uiExit)
|| (REGR_SUCCESS != (iRetServPid = regrGetServPid(
(char*)g_acDataPath,
MAXPGPATH,
puiPidChanged))))
{
fprintf(stderr, _("\n Could not get the Resource usage details. "
"Internal error code: %d. regrGetServPid EC: %d. "
"Exit flag status: %u. \n"),
iRet, iRetServPid, uiExit);
return REGR_ERRCODE_RESRC_STAT_FETCH_FAILED;
}
if (true == *puiPidChanged)
{
/* Server would have restarted. Thus PID changed. So reset the
* values based on the old PID */
(void)memset(&g_stResourceUsageDetails,
0, sizeof(REGR_AVG_RSRCE_USAGE_STRU));
}
uiExit = true;
} while (true);
return REGR_SUCCESS;
}
/* Get the subsequent reading for the CPU and Memory usage. For calculating
* the CPU usage, we need two set of readings of REGR_RESRC_STAT_STRU instance*/
static void regrGetSubsequentUsage()
{
unsigned int uiPidChanged = false;
double dUcpuUsagePct = 0.0;
double dScpuUsagePct = 0.0;
/* If the first CPU usage reading is not available, then there is no point
* in getting second CPU usage readings */
if (0 == g_stPrevCpuUsage.ulCpuTotalTime)
{
return;
}
/* Getting the CPU Usage details */
if (REGR_SUCCESS != regrGetRsrcUsageWithRepeatOnFail(&g_stCurrCpuUsage,
&uiPidChanged))
{
fprintf(stderr, _("Could not get the CPU Usage info!! \n"));
return;
}
if (false == uiPidChanged)
{
/* In case of Server restart i.e. uiPidChanged is TRUE, g_stPrevCpuUsage
* value is based on the old pid which can't be compared against the
* current value. So, cannot call the below function. Otherwise,
* getting the cpu usage in percentage, for the previous and
* current values */
regrCalcCpuUsagePct(&g_stCurrCpuUsage,
&g_stPrevCpuUsage,
&dUcpuUsagePct,
&dScpuUsagePct);
}
/* The current reading is set as the 'previous' reading so that next time
* this can be used to calculate the CPU %, because CPU % calculation
* requires two set of readings. */
g_stPrevCpuUsage = g_stCurrCpuUsage;
/* Memory usage is added up (in MBs) and finally the sum will be divided by
* the total reading count to get the average */
g_stResourceUsageDetails.dAvgMemUsage
+= (g_stCurrCpuUsage.rss / REGR_MCR_SIZE_1MB);
/* Adding up the Memory percents */
g_stResourceUsageDetails.dAvgMemUsagePct += g_stCurrCpuUsage.dMemPct;
/* Adding up the User CPU usage detail */
g_stResourceUsageDetails.dUAvgcpuUsage += dUcpuUsagePct;
/* Adding up the Kernel(system) CPU usage detail */
g_stResourceUsageDetails.dSAvgcpuUsage += dScpuUsagePct;
/* Updating the total sample counts */
g_stResourceUsageDetails.ulCount++;
}
/*
* Add an item at the end of a stringlist.
*/
void
add_stringlist_item(_stringlist** listhead, const char* str)
{
_stringlist* newentry = (_stringlist*)malloc(sizeof(_stringlist));
_stringlist* oldentry;
newentry->str = strdup(str);
newentry->next = NULL;
if (*listhead == NULL)
{ *listhead = newentry; }
else
{
for (oldentry = *listhead; oldentry->next; oldentry = oldentry->next)
/* skip */ ;
oldentry->next = newentry;
}
}
/*
* 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 * tmp_token = NULL;
char *token = strtok_r(sc, delim, &tmp_token);
while (token)
{
add_stringlist_item(listhead, token);
token = strtok_r(NULL, delim, &tmp_token);
}
free(sc);
}
/* Trim LHS and RHS of the passed string for white spaces */
static void
regrTrimString(char** ppcString)
{
while ((**ppcString == ' ') || (**ppcString == '\t'))
(*ppcString)++;
if ((**ppcString) != '\0')
{
int len = strlen(*ppcString);
char *ptr = *ppcString + (len - 1);
while (((*ptr == ' ')
|| (*ptr == '\t')
|| (*ptr == '\r')
|| (*ptr == '\n'))
&& ((len--) > 0))
ptr--;
*(ptr + 1) = '\0';
}
}
/*
* Print a progress banner on stdout.
*/
static void
header(const char* fmt, ...)
{
char tmp[1024];
va_list ap;
va_start(ap, fmt);
(void)vsnprintf(tmp, sizeof(tmp), fmt, ap);
va_end(ap);
fprintf(stdout, "============== %-38s ==============\n", tmp);
(void)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);
(void)vfprintf(stdout, fmt, ap);
(void)fflush(stdout);
va_end(ap);
if (logfile)
{
va_start(ap, fmt);
(void)vfprintf(logfile, fmt, ap);
va_end(ap);
}
}
/*
* Done "doing something ..."
*/
static void
status_end(void)
{
fprintf(stdout, "\n");
(void)fflush(stdout);
if (logfile)
{ fprintf(logfile, "\n"); }
}
#ifdef PGXC
/*
* Stop GTM process
*/
static void
stop_gtm(void)
{
const char* data_folder = get_node_info_name(0, GTM, false);
int r;
char buf[MAXPGPATH * 2];
/* On Windows, system() seems not to force fflush, so... */
(void)fflush(stdout);
(void)fflush(stderr);
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gtm_ctl\" stop -Z gtm -D \"%s/%s\" -m fast" SYSTEMQUOTE,
bindir, temp_install, data_folder);
r = system(buf);
if (r != 0)
{
memset(buf, 0, sizeof(buf));
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gtm_ctl\" stop -Z gtm -D \"%s/%s\" -m i" SYSTEMQUOTE,
bindir, temp_install, data_folder);
r = system(buf);
if (r != 0)
{
fprintf(stderr, _("\n%s: could not stop GTM: exit code was %d\n"),
progname, r);
exit(2); /* not exit(), that would be recursive */
}
}
}
static void
gen_startstop_script()
{
int i;
int port_number;
char buf[MAXPGPATH * 4];
if(temp_install == NULL)
return;
(void)snprintf(buf, sizeof(buf), "%s/start.sh", temp_install);
start_script = fopen(buf, PG_BINARY_W);
(void)snprintf(buf, sizeof(buf), "%s/stop.sh", temp_install);
stop_script = fopen(buf, PG_BINARY_W);
const char* data_folder = get_node_info_name(0, GTM, false);
fprintf(start_script, "#! /bin/bash \n");
fprintf(start_script, "rm -rf /tmp/.s.PG* \n");
fprintf(start_script,
"find %s -name postmaster.pid | xargs rm -rf \n",
temp_install);
fprintf(start_script,
"%s/gs_gtm -n gtm -D %s/%s -p %d & \n",
bindir, temp_install, data_folder, myinfo.gtm_port);
fprintf(stop_script, "#! /bin/bash \n");
for (i = 0 ; i < myinfo.co_num ; i++)
{
data_folder = get_node_info_name(i, COORD, false);
port_number = myinfo.co_port[i];
fprintf(start_script,
"%s/gaussdb --coordinator -i -p %d -D %s/%s -c log_statement=all -c logging_collector=true -c log_filename='cn.log' & \n",
bindir,
port_number,
temp_install, data_folder);
fprintf(stop_script,
"%s/gs_ctl stop -D %s/%s \n",
bindir, temp_install, data_folder);
}
for (i = 0 ; i < myinfo.dn_num ; i++)
{
data_folder = get_node_info_name(i, DATANODE, false);
port_number = myinfo.dn_port[i];
fprintf(start_script,
"%s/gaussdb --datanode -i -p %d -D %s/%s -c log_statement=all -c logging_collector=true -c log_filename='dn.log' & \n",
bindir,
port_number,
temp_install, data_folder);
fprintf(stop_script,
"%s/gs_ctl stop -D %s/%s \n",
bindir, temp_install, data_folder);
}
const char* gtm_folder = get_node_info_name(0, GTM, false);
fprintf(stop_script, "%s/gtm_ctl stop -Z gtm -D %s/%s -m fast \n",
bindir, temp_install, gtm_folder);
(void)snprintf(buf, sizeof(buf), "chmod +x %s/start.sh", temp_install);
(void)system(buf);
(void)snprintf(buf, sizeof(buf), "chmod +x %s/stop.sh", temp_install);
(void)system(buf);
fclose(start_script);
fclose(stop_script);
}
/*
* Stop the given node
*/
static void
stop_node(int i, int type, bool standby)
{
char* data_folder = get_node_info_name(i, type, false);
char buf[MAXPGPATH * 2];
int r;
/* On Windows, system() seems not to force fflush, so... */
(void)fflush(stdout);
(void)fflush(stderr);
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_ctl\" stop -D \"%s/%s\" -s -m fast" SYSTEMQUOTE,
bindir, temp_install, data_folder);
r = system(buf);
if (r != 0)
{
memset(buf, 0, sizeof(buf));
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_ctl\" stop -D \"%s/%s\" -s -m i" SYSTEMQUOTE,
bindir, temp_install, data_folder);
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 would be recursive */
}
}
if (!standby)
{
free(data_folder);
return;
}
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_ctl\" stop -D \"%s/%s_standby\" -s -m fast" SYSTEMQUOTE,
bindir, temp_install, data_folder);
r = system(buf);
if (r != 0)
{
memset(buf, 0, sizeof(buf));
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_ctl\" stop -D \"%s/%s_standby\" -s -m i" SYSTEMQUOTE,
bindir, temp_install, data_folder);
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 would be recursive */
}
}
free(data_folder);
}
#endif
/*
* shut down temp postmaster
*/
static void
stop_postmaster(void)
{
int i;
printf("stop postmaster now!\n");
if (postmaster_running)
{
#ifdef PGXC
/*
* It is necessary to stop first the Coordinator 1,
* Then other nodes are stopped nicely.
* This is due to connection dependencies between nodes.
*/
for (i = 0 ; i < myinfo.co_num ; i++)
{ stop_node(i, COORD, false); }
for (i = 0 ; i < myinfo.dn_num ; i++)
{ stop_node(i, DATANODE, standby_defined); }
for (i = 0; i < myinfo.shell_count; i++)
{ (void)kill(myinfo.shell_pid[i], SIGKILL); }
/* Stop GTM at the end */
stop_gtm();
#else
/* We use pg_ctl to issue the kill and wait for stop */
char buf[MAXPGPATH * 2];
int r;
/* On Windows, system() seems not to force fflush, so... */
fflush(stdout);
fflush(stderr);
snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_ctl\" stop -D \"%s/data\" -s -m fast" SYSTEMQUOTE,
bindir, temp_install);
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 would be recursive */
}
#endif
postmaster_running = false;
}
}
#ifdef PGXC
/*
* Handy subroutine for setting an environment variable "var" to "val"
*/
/*lint -e429*/
static void
doputenv(const char* var, const char* val)
{
char* s = (char*)malloc(strlen(var) + strlen(val) + 2);
sprintf(s, "%s=%s", var, val);
(void)putenv(s);
}
/*lint +e429*/
/*
* Get node port of associated node
*/
static int
get_port_number(int i, int type)
{
switch (type)
{
case COORD:
return myinfo.co_port[i];
case DATANODE:
return myinfo.dn_port[i];
case GTM:
return myinfo.gtm_port;
default:
/* Should not happen */
return -1;
}
}
/*
* Get PID number for given node
*/
static PID_TYPE
get_node_pid(int i, int type)
{
switch (type)
{
case COORD:
return myinfo.co_pid[i];
case DATANODE:
return myinfo.dn_pid[i];
case GTM:
return myinfo.gtm_pid;
default:
/* Should not happen */
return -1;
}
}
/*
* Start GTM process
*/
static void
start_gtm(void)
{
char buf[MAXPGPATH * 4];
const char* data_folder = get_node_info_name(0, GTM, false);
PID_TYPE node_pid;
FILE* gtm_conf_file;
/*
* Before starting GTM, set up an empty configuration file as
* all the necessary parameters are provided in command.
*/
// snprintf(buf, sizeof(buf), "%s/%s", temp_install, data_folder);
// make_directory(buf);
(void)snprintf(buf, sizeof(buf), "%s/%s/gtm.conf", temp_install, data_folder);
gtm_conf_file = fopen(buf, PG_BINARY_W);
if (gtm_conf_file == NULL)
{
fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
progname, buf, strerror(errno));
exit_nicely(2);
}
if (fclose(gtm_conf_file))
{
fprintf(stderr, _("%s: could not write file \"%s\": %s\n"),
progname, buf, strerror(errno));
exit_nicely(2);
}
header(_("starting GTM process"));
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_gtm\" -n \"gtm\" -D \"%s/%s\" -p %d > \"%s/log/gtm.log\" 2>&1" SYSTEMQUOTE,
bindir, temp_install, data_folder, myinfo.gtm_port,
outputdir);
/* Start process */
node_pid = spawn_process(buf);
if (node_pid == INVALID_PID)
{
fprintf(stderr, _("\n%s: could not spawn GTM: %s\n"),
progname, strerror(errno));
exit_nicely(2);
}
/* Save static PID number */
myinfo.gtm_pid = node_pid;
//set_node_pid(PGXC_GTM, node_pid);
}
/*
* Start given node
*/
static void
start_my_node(int i, int type, bool is_main, bool standby)
{
int port_number;
char* data_folder = get_node_info_name(i, type, false);
if (type == COORD)
{ port_number = myinfo.co_port[i]; }
else
{ port_number = myinfo.dn_port[i]; }
PID_TYPE node_pid;
char buf[MAXPGPATH * 4];
/* Start the node */
if (is_main)
{
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gaussdb\" %s -i -p %d -D \"%s/%s\"%s -c log_statement=all -c logging_collector=true -c \"listen_addresses=%s\" > \"%s/log/postmaster_%s.log\" 2>&1" SYSTEMQUOTE,
bindir,
(type == COORD) ? "--coordinator" : "--datanode",
port_number,
temp_install, data_folder,
debug ? " -d 5" : "",
hostname ? hostname : "*",
outputdir,
data_folder);
}
else
{
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gaussdb\" %s -i -p %d -c log_statement=all -M %s -c logging_collector=true -D \"%s/%s\"%s > \"%s/log/postmaster_%s.log\" 2>&1" SYSTEMQUOTE,
bindir,
(type == COORD) ? "--coordinator" : "--datanode",
port_number,
(standby) ? "primary" : "normal",
temp_install, data_folder,
debug ? " -d 5" : "",
outputdir,
data_folder);
}
node_pid = spawn_process(buf);
if (node_pid == INVALID_PID)
{
fprintf(stderr, _("\n%s: could not spawn postmaster: %s\n"),
progname, strerror(errno));
exit_nicely(2);
}
/* Save static PID number */
if (type == COORD)
{ myinfo.co_pid[i] = node_pid; }
else
{ myinfo.dn_pid[i] = node_pid; }
//set_node_pid(node,node_pid);
free(data_folder);
}
/*
* Use gs_ctl build to setup datanode replication if requested.
*/
static void
setup_datanode_replication(int index)
{
char* data_folder = get_node_info_name(index, DATANODE, false);
PID_TYPE node_pid;
char buf[MAXPGPATH * 4];
if (!standby_defined)
{
free(data_folder);
return; /* nothing to do */
}
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_ctl\" build -b full -Z datanode -D \"%s/%s_standby\" > \"%s/log/build_%s_standby.log\" 2>&1" SYSTEMQUOTE,
bindir,
temp_install, data_folder,
outputdir,
data_folder);
node_pid = spawn_process(buf);
if (node_pid == INVALID_PID)
{
fprintf(stderr, _("\n%s: could not spawn gs_ctl: %s\n"),
progname, strerror(errno));
exit_nicely(2);
}
free(data_folder);
}
static
char* get_node_info_name(int i, int type, bool is_node_name)
{
char* mybuf;
int len = 100 * sizeof(char);
mybuf = (char*)malloc(len);
switch (type)
{
case COORD:
{
if (is_node_name)
{
(void)snprintf(mybuf, len, "coordinator%d", i + 1);
}
else
{
(void)snprintf(mybuf, len, "coordinator%d", i + 1);
}
}
break;
case DATANODE:
{
if (is_node_name)
{
(void)snprintf(mybuf, len, "datanode%d", i + 1);
}
else
{
(void)snprintf(mybuf, len, "datanode%d", i + 1);
}
}
break;
case GTM:
{
(void)snprintf(mybuf, len, "datagtm");
}
break;
}
return mybuf;
}
/*
* Initialize GTM
*/
static void
init_gtm(void)
{
const char* data_folder = get_node_info_name(0, GTM, false);
char buf[MAXPGPATH * 4];
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_initgtm\" -Z gtm -D \"%s/%s\" --noclean%s > \"%s/log/initgtm.log\" 2>&1 &" SYSTEMQUOTE,
bindir, temp_install, data_folder,
debug ? " --debug" : "",
outputdir);
if (system(buf))
{
fprintf(stderr, _("\n%s: gs_initgtm failed\nExamine %s/log/initgtm.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
exit_nicely(2);
}
}
static void
initdb_node_info(bool standby)
{
int i;
for (i = 0 ; i < myinfo.co_num; i++)
{
char buf[MAXPGPATH * 4];
char* data_folder = get_node_info_name(i, COORD, false);
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_initdb\" --nodename %s -w \"gauss@123\" -D \"%s/%s\" -L \"%s\" --noclean%s%s > \"%s/log/initdb.log\" 2>&1" SYSTEMQUOTE,
bindir, (char*)get_node_info_name(i, COORD, true), temp_install, data_folder, datadir,
debug ? " --debug" : "",
nolocale ? " --no-locale" : "",
outputdir);
if (system(buf))
{
fprintf(stderr, _("\n%s: gs_initdb failed\nExamine %s/log/initdb.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
exit_nicely(2);
}
free(data_folder);
}
for (i = 0 ; i < myinfo.dn_num; i++)
{
char buf[MAXPGPATH * 4];
char* data_folder = get_node_info_name(i, DATANODE, false);
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_initdb\" --nodename %s -w \"gauss@123\" -D \"%s/%s\" -L \"%s\" --noclean%s%s > \"%s/log/initdb.log\" 2>&1" SYSTEMQUOTE,
bindir, (char*)get_node_info_name(i, DATANODE, true), temp_install, data_folder, datadir,
debug ? " --debug" : "",
nolocale ? " --no-locale" : "",
outputdir);
if (system(buf))
{
fprintf(stderr, _("\n%s: gs_initdb failed\nExamine %s/log/initdb.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
exit_nicely(2);
}
free(data_folder);
}
/* no standby configed, skip to init standby. */
if (!standby)
return;
for (i = 0; i < myinfo.dn_num; i++)
{
char buf[MAXPGPATH * 4];
char* data_folder = get_node_info_name(i, DATANODE, false);
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s/gs_initdb\" --nodename %s -w \"gauss@123\" -D \"%s/%s_standby\" -L \"%s\" --noclean%s%s > \"%s/log/initdb.log\" 2>&1" SYSTEMQUOTE,
bindir, (char*)get_node_info_name(i, DATANODE, true), temp_install, data_folder, datadir,
debug ? " --debug" : "",
nolocale ? " --no-locale" : "",
outputdir);
if (system(buf))
{
fprintf(stderr, _("\n%s: gs_initdb failed\nExamine %s/log/initdb.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
exit_nicely(2);
}
free(data_folder);
}
}
int get_local_ip(char * local_ip)
{
char buf[100];
struct addrinfo hints;
struct addrinfo *res, *curr;
struct sockaddr_in *sa;
errno_t ss_rc = 0;
ss_rc = memset_s(&hints,sizeof(addrinfo),0x0,sizeof(addrinfo));
securec_check_c(ss_rc, "\0", "\0");
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_INET;
if (gethostname(buf, sizeof (buf)) < 0)
{
return -1;
}
if(getaddrinfo(buf, 0, &hints, &res) != 0)
{
return -1;
}
if(NULL==res)
{
return -1;
}
curr = res;
sa = (struct sockaddr_in *)curr->ai_addr;
(void)snprintf(local_ip,LOCAL_IP_LEN,"%s",inet_ntop(AF_INET, &sa->sin_addr.s_addr, buf, sizeof (buf)));
if(res)
{
freeaddrinfo(res);
}
return 0;
}
static void
initdb_node_config_file(bool standby)
{
int i;
char local_ip[LOCAL_IP_LEN];
for (i = 0 ; i < myinfo.co_num ; i++)
{
char* data_folder = get_node_info_name(i, COORD, false);
FILE* pg_conf;
char buf[MAXPGPATH * 4];
(void)snprintf(buf, sizeof(buf), "%s/%s/postgresql.conf", temp_install, data_folder);
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);
(void)snprintf(buf, sizeof(buf), "port = %d\n", myinfo.co_port[i]);
fputs(buf, pg_conf);
/*
* Cluster uses 2PC for write transactions involving multiple nodes
* This has to be set at least to a value corresponding to the maximum
* number of tests run in parallel.
*/
fputs("max_prepared_transactions = 50\n", pg_conf);
/* Set GTM connection information */
fputs("gtm_host = 'localhost'\n", pg_conf);
(void)snprintf(buf, sizeof(buf), "gtm_port = %d\n", myinfo.gtm_port);
fputs(buf, pg_conf);
(void)snprintf(buf, sizeof(buf), "pooler_port = %d\n", myinfo.co_pool_port[i]);
fputs(buf, pg_conf);
/*Turn on the guc control support_extended_features*/
fputs("support_extended_features = on\n", pg_conf);
/* To satisfy LLT, we need to modify the initial password */
if(change_password)
fputs("modify_initial_password = true\n", pg_conf);
/* always use LLVM optimization */
fputs("codegen_cost_threshold=0\n", pg_conf);
if (dop > 0)
{
(void)snprintf(buf, sizeof(buf), "query_dop = %d\n", dop);
fputs(buf, 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);
memset(local_ip,0,sizeof(local_ip));
if(0!=get_local_ip(local_ip))
{
strncpy(local_ip, "localhost", LOCAL_IP_LEN);
}
(void)snprintf(buf, sizeof(buf), "%s/%s/pg_hba.conf", temp_install, data_folder);
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);
(void)snprintf(buf, sizeof(buf), " host all all %s/32 trust\n", local_ip);
fputs(buf, pg_conf);
fclose(pg_conf);
free(data_folder);
}
for (i = 0; i < myinfo.dn_num ; i++)
{
char* data_folder = get_node_info_name(i, DATANODE, false);
FILE* pg_conf;
char buf[MAXPGPATH * 4];
(void)snprintf(buf, sizeof(buf), "%s/%s/postgresql.conf", temp_install, data_folder);
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);
(void)snprintf(buf, sizeof(buf), "port = %d\n", myinfo.dn_port[i]);
fputs(buf, pg_conf);
/*
* Cluster uses 2PC for write transactions involving multiple nodes
* This has to be set at least to a value corresponding to the maximum
* number of tests run in parallel.
*/
fputs("max_prepared_transactions = 50\n", pg_conf);
(void)snprintf(buf, sizeof(buf), "pooler_port = %d\n", myinfo.dn_pool_port[i]);
fputs(buf, pg_conf);
if (comm_tcp_mode == false)
fputs("comm_tcp_mode = false\n", pg_conf);
/*Turn on the guc control support_extended_features*/
fputs("support_extended_features = on\n", pg_conf);
/* To satisfy LLT, we need to modify the initial password */
if(change_password)
fputs("modify_initial_password = true\n", pg_conf);
/* always use LLVM optimization */
fputs("codegen_cost_threshold=0\n", pg_conf);
if (dop > 0)
{
(void)snprintf(buf, sizeof(buf), "query_dop = %d\n", dop);
fputs(buf, pg_conf);
}
/* Set GTM connection information */
fputs("gtm_host = 'localhost'\n", pg_conf);
(void)snprintf(buf, sizeof(buf), "gtm_port = %d\n", myinfo.gtm_port);
fputs(buf, pg_conf);
if (standby)
{
(void)snprintf(buf, sizeof(buf),
"replconninfo1 = 'localhost=127.0.0.1 localport=%d remotehost=127.0.0.1 remoteport=%d'\n",
myinfo.dn_primary_port[i], myinfo.dn_standby_port[i]);
fputs(buf, pg_conf);
(void)snprintf(buf, sizeof(buf),
"replconninfo2 = 'localhost=127.0.0.1 localport=%d remotehost=127.0.0.1 remoteport=%d'\n",
myinfo.dn_primary_port[i], myinfo.dn_secondary_port[i]);
fputs(buf, 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);
memset(local_ip,0,sizeof(local_ip));
if(0!=get_local_ip(local_ip))
{
strncpy(local_ip, "localhost", LOCAL_IP_LEN);
}
(void)snprintf(buf, sizeof(buf), "%s/%s/pg_hba.conf", temp_install, data_folder);
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);
(void)snprintf(buf, sizeof(buf), " host all all %s/32 trust\n", local_ip);
fputs(buf, pg_conf);
fclose(pg_conf);
free(data_folder);
}
if (!standby)
return;
for (i = 0; i < myinfo.dn_num ; i++)
{
char* data_folder = get_node_info_name(i, DATANODE, false);
FILE* pg_conf;
char buf[MAXPGPATH * 4];
(void)snprintf(buf, sizeof(buf), "%s/%s_standby/postgresql.conf", temp_install, data_folder);
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);
(void)snprintf(buf, sizeof(buf), "port = %d\n", myinfo.dns_port[i]);
fputs(buf, pg_conf);
(void)snprintf(buf, sizeof(buf),
"replconninfo1 = 'localhost=127.0.0.1 localport=%d remotehost=127.0.0.1 remoteport=%d'\n",
myinfo.dn_standby_port[i], myinfo.dn_primary_port[i]);
fputs(buf, pg_conf);
(void)snprintf(buf, sizeof(buf),
"replconninfo2 = 'localhost=127.0.0.1 localport=%d remotehost=127.0.0.1 remoteport=%d'\n",
myinfo.dn_standby_port[i], myinfo.dn_secondary_port[i]);
fputs(buf, 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);
free(data_folder);
}
}
static void
psql_command(const char* database, const char* query, ...)
{
char query_formatted[1024];
char query_escaped[2048];
char psql_cmd[MAXPGPATH + 2048];
va_list args;
char* s;
char* d;
/* Generate the query with insertion of sprintf arguments */
va_start(args, query);
(void)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 */
(void)snprintf(psql_cmd, sizeof(psql_cmd),
SYSTEMQUOTE "\"%s%sgsql\" -X -p %d -c \"%s\" \"%s\"" SYSTEMQUOTE,
psqldir ? psqldir : "",
psqldir ? "/" : "",
myinfo.co_port[0],
query_escaped,
database);
if (system(psql_cmd) != 0)
{
/* psql probably already reported the error */
fprintf(stderr, _("command failed: %s\n"), psql_cmd);
exit_nicely(2);
}
}
/*
* Issue a command via psql, connecting to the specified database on wanted node
* Since we use system(), this doesn't return until the operation finishes
* This code is duplicated with psql_command but this way code impact is limited.
*/
static void
psql_command_node(const char* database, int i, int type, const char* query, ...)
{
char query_formatted[1024];
char query_escaped[2048];
char psql_cmd[MAXPGPATH + 2048];
va_list args;
char* s;
char* d;
/* Generate the query with insertion of sprintf arguments */
va_start(args, query);
(void)vsnprintf((char*)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 */
(void)snprintf(psql_cmd, sizeof(psql_cmd),
SYSTEMQUOTE "\"%s%sgsql\" -X -p %d -c \"%s\" \"%s\"" SYSTEMQUOTE,
psqldir ? psqldir : "",
psqldir ? "/" : "",
get_port_number(i, type),
query_escaped,
database);
if (system(psql_cmd) != 0)
{
/* psql probably already reported the error */
fprintf(stderr, _("command failed: %s\n"), psql_cmd);
exit_nicely(2);
}
}
/*
* strdup() and malloc() replacements that prints an error and exits
* if something goes wrong. Can never return NULL.
*/
static char *
xstrdup(const char *s)
{
char *result;
result = strdup(s);
if (!result)
{
fprintf(stderr, _("%s: out of memory\n"), progname);
exit(1);
}
return result;
}
/*
* find the current user
*
* on unix make sure it isn't really root
*/
static char *
get_id(void)
{
struct passwd *pw;
if (geteuid() == 0) /* 0 is root's uid */
{
fprintf(stderr, _("%s: cannot be run as root\n"
"Please log in (using, e.g., \"su\") as the "
"(unprivileged) user that will\n"
"own the server process.\n"),
progname);
exit(1);
}
pw = getpwuid(geteuid());
if (!pw)
{
fprintf(stderr, _("%s: could not obtain information about current user: %s\n"),
progname, strerror(errno));
exit(1);
}
return xstrdup(pw->pw_name);
}
static void
rebuild_node_group()
{
int i = 0;
int j = 0;
char nname_list[256];
char create_nodegroup[512];
char drop_nodegroup[512];
char *pname = NULL;
sprintf(nname_list, "(");
for (j = 0; j < myinfo.dn_num; j++)
{
pname = get_node_info_name(j, DATANODE, true);
strcat(nname_list, pname);
if (j < myinfo.dn_num-1)
strcat(nname_list, ",");
free(pname);
pname = NULL;
}
strcat(nname_list, ")");
sprintf(drop_nodegroup, "DROP NODE GROUP group1");
sprintf(create_nodegroup, "CREATE NODE GROUP group1 WITH %s", nname_list);
fprintf(stderr, "%s\n", drop_nodegroup);
fprintf(stderr, "%s\n", create_nodegroup);
for (i = 0; i < myinfo.co_num; i++)
{
psql_command_node("postgres", i, COORD, "%s", drop_nodegroup);
psql_command_node("postgres", i, COORD, "%s", create_nodegroup);
}
for (i = 0 ; i < myinfo.co_num ; i ++)
{
psql_command_node("postgres", i, COORD, "SELECT pgxc_pool_reload();");
psql_command_node("postgres", i, COORD, "SELECT oid, node_name, node_type,"
"node_host, node_port, node_host1, node_port1, "
"sctp_port, control_port, sctp_port1, control_port1 "
"from pgxc_node;");
}
}
/*
* Setup connection information to remote nodes for coordinator running regression
*/
static void
setup_connection_information(bool standby)
{
header(_("setting connection information"));
/* Datanodes on Coordinator 1*/
header(_("sleeping"));
if (temp_config && strcmp(temp_config, "./make_wlmcheck_postgresql.conf") == 0)
sleep(25);
sleep(5);
int i, j;
char nname_list[256], sql_tmp[512];
char *pname;
char local_ip[LOCAL_IP_LEN];
memset(local_ip,0,sizeof(local_ip));
if(0!=get_local_ip(local_ip))
{
strncpy(local_ip, "localhost", LOCAL_IP_LEN);
}
for (i = 0 ; i < myinfo.co_num ; i ++)
{
for (j = 0 ; j < myinfo.dn_num ; j++)
{
char *node_info_name = get_node_info_name(j, DATANODE, true);
if (standby)
{
psql_command_node("postgres", i, COORD, "CREATE NODE %s WITH (type='datanode', "
"host='%s', port=%d, host1='%s', port1=%d, "
"sctp_port=%d, control_port=%d, sctp_port1=%d, control_port1=%d);",
node_info_name,
local_ip, get_port_number(j, DATANODE),
local_ip, myinfo.dns_port[j],
myinfo.dn_sctp_port[j], myinfo.dn_ctl_port[j],
myinfo.dns_sctp_port[j], myinfo.dns_ctl_port[j]);
}
else
{
psql_command_node("postgres", i, COORD, "CREATE NODE %s WITH (type='datanode', "
"host='%s', port=%d, "
"sctp_port=%d, control_port=%d);",
node_info_name,
local_ip, get_port_number(j, DATANODE),
myinfo.dn_sctp_port[j], myinfo.dn_ctl_port[j]);
}
free(node_info_name);
}
}
sprintf(nname_list, "(");
for (j = 0; j < myinfo.dn_num; j++)
{
pname = get_node_info_name(j,DATANODE,true);
strcat(nname_list,pname);
if(j<myinfo.dn_num-1)
strcat(nname_list,",");
free(pname);
pname = NULL;
}
strcat(nname_list,")");
sprintf(sql_tmp, "CREATE NODE GROUP group1 WITH %s", nname_list);
fprintf(stderr,"%s\n", sql_tmp);
for (i = 0; i <myinfo.co_num; i++)
psql_command_node("postgres", i, COORD, "%s", sql_tmp);
header(_("sleeping"));
sleep(10);
/* Remote Coordinator on other Coordinator */
for (i = 0 ; i < myinfo.co_num ; i ++)
{
for (j = 0 ; j < myinfo.co_num ; j++)
{
if (j != i)
{
char *node_info_name = get_node_info_name(j, COORD, true);
psql_command_node("postgres", i, COORD, "CREATE NODE %s WITH (HOST = '%s',"
" type = 'coordinator', PORT = %d);",
node_info_name,
"localhost",
get_port_number(j, COORD));
free(node_info_name);
}
}
}
/* We only need do this in MAKEFASTCHECK */
if(change_password)
{
char *current_user = get_id();
sleep(10);
psql_command("postgres", "Alter user \"%s\" identified by 'Gauss@123' replace 'gauss@123';", current_user);
free(current_user);
}
/* Then reload the connection data */
for (i = 0 ; i < myinfo.co_num ; i ++)
{
psql_command_node("postgres", i, COORD, "SELECT pgxc_pool_reload();");
psql_command_node("postgres", i, COORD, "SELECT oid, node_name, node_type,"
"node_host, node_port, node_host1, node_port1, "
"sctp_port, control_port, sctp_port1, control_port1 "
"from pgxc_node;");
}
}
/*
* Check if given node has failed during startup
*/
static void
check_node_fail(int i, int type)
{
PID_TYPE pid_number = INVALID_PID;
switch (type)
{
case DATANODE:
pid_number = myinfo.co_pid[i];
break;
case GTM:
pid_number = myinfo.gtm_pid;
break;
case COORD:
pid_number = myinfo.dn_pid[i];
break;
}
#ifndef WIN32
if (kill(pid_number, 0) != 0)
#else
if (WaitForSingleObject(pid_number, 0) == WAIT_OBJECT_0)
#endif /* WIN32 */
{
fprintf(stderr, _("\n%s: postmaster failed\nExamine %s/log/postmaster_%d.log for the reason\n"), progname, outputdir, pid_number);
exit_nicely(2);
}
}
/*
* Kill given node but do not exit
*/
static void
kill_node(int i, int type)
{
PID_TYPE pid_number = INVALID_PID;
switch (type)
{
case DATANODE:
pid_number = myinfo.co_pid[i];
break;
case GTM:
pid_number = myinfo.gtm_pid;
break;
case COORD:
pid_number = myinfo.dn_pid[i];
break;
case SHELL:
pid_number = myinfo.shell_pid[i];
break;
}
fprintf(stderr, _("\n%s: postmaster did not respond within 60 seconds\nExamine %s/log/postmaster_%d.log for the reason\n"), progname, outputdir, pid_number);
#ifndef WIN32
if (kill(pid_number, SIGKILL) != 0 &&
errno != ESRCH)
fprintf(stderr, _("\n%s: could not kill failed postmaster: %s\n"),
progname, strerror(errno));
#else
if (TerminateProcess(pid_number, 255) == 0)
fprintf(stderr, _("\n%s: could not kill failed postmaster: %lu\n"),
progname, GetLastError());
#endif
}
#endif
/*
* Always exit through here, not through plain exit(), to ensure we make
* an effort to shut down a temp postmaster
*/
void
exit_nicely(int code)
{
if (temp_install && clean)
{ stop_postmaster(); }
exit(code);
}
/*
* Check whether string matches pattern
*
* In the original shell script, this function was implemented using expr(1),
* which provides basic regular expressions restricted to match starting at
* the string start (in conventional regex terms, there's an implicit "^"
* at the start of the pattern --- but no implicit "$" at the end).
*
* For now, we only support "." and ".*" as non-literal metacharacters,
* because that's all that anyone has found use for in resultmap. This
* code could be extended if more functionality is needed.
*/
static bool
string_matches_pattern(const char* str, const char* pattern)
{
while (*str && *pattern)
{
if (*pattern == '.' && pattern[1] == '*')
{
pattern += 2;
/* Trailing .* matches everything. */
if (*pattern == '\0')
{ return true; }
/*
* Otherwise, scan for a text position at which we can match the
* rest of the pattern.
*/
while (*str)
{
/*
* Optimization to prevent most recursion: don't recurse
* unless first pattern char might match this text char.
*/
if (*str == *pattern || *pattern == '.')
{
if (string_matches_pattern(str, pattern))
{ return true; }
}
str++;
}
/*
* End of text with no match.
*/
return false;
}
else if (*pattern != '.' && *str != *pattern)
{
/*
* Not the single-character wildcard and no explicit match? Then
* time to quit...
*/
return false;
}
str++;
pattern++;
}
if (*pattern == '\0')
{ return true; } /* end of pattern, so declare match */
/* End of input string. Do we have matching pattern remaining? */
while (*pattern == '.' && pattern[1] == '*')
{ pattern += 2; }
if (*pattern == '\0')
{ return true; } /* end of pattern, so declare match */
return false;
}
/*
* Replace all occurances 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)
{
char* ptr;
while ((ptr = strstr(string, replace)) != NULL)
{
char* cdup = strdup(string);
(void)strlcpy(string, cdup, ptr - string + 1);
strcat(string, replacement);
strcat(string, cdup + (ptr - string) + strlen(replace));
free(cdup);
}
}
/*
* Check for sub directories within the input folders and convert the
* .source files to appropriate .sql files within the sql folder.
*
* Return true if the object is an existing directory otherwise return false
*
*/
static bool
CheckForSubdirAndConvertSourceFiles(char* source,
char* destination,
char* pcSourceSubdir,
char* pcDestDir,
char* pcDestSubdir,
char* indir,
char* object,
char* suffix)
{
(void)snprintf(source, MAXPGPATH, "%s/%s", indir, object);
if (!directory_exists(source))
return false;
/* If the directory name is starting with '.' char, then it is meta folder
* like .svn , .gitignore etc. These folders can't be considered for test
* script scanning. So skip them and return false indicating sub directory
* is not found for 'pcSourceSubdir' */
if (*object == '.')
return false;
sprintf(source, "%s/%s", pcSourceSubdir, object);
sprintf(destination, "%s/%s", pcDestSubdir, object);
convertSourcefilesIn(source, pcDestDir, destination, suffix);
return true;
}
bool is_cmdline(const char* src)
{
const char* ptr = src;
while (isspace(*ptr))
{ ptr++; }
return (strncmp(ptr, "@@", 2) == 0);
}
static void get_cmdline(const char* src, char* dsr)
{
const char* ptr = src;
while (isspace(*ptr))
{ ptr++; }
if (!is_cmdline(ptr))
{ return; }
ptr += 2;
strcpy(dsr, ptr);
}
static void
exec_cmds_from_inputfiles()
{
char indir[MAXPGPATH];
struct stat st;
int ret;
char** name;
char** names;
int count = 0;
FILE* infile;
(void)snprintf(indir, MAXPGPATH, "%s/%s", inputdir, "input");
/* 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_nicely(2); }
/* finally loop on each file and do the replacement */
for (name = names; *name; name++)
{
char srcfile[MAXPGPATH];
char prefix[MAXPGPATH];
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 */
(void)snprintf(prefix, strlen(*name) - 6, "%s", *name);
(void)snprintf(srcfile, MAXPGPATH, "%s/%s", indir, *name);
infile = fopen(srcfile, "r");
if (!infile)
{
fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
progname, srcfile, strerror(errno));
exit_nicely(2);
}
while (fgets(line, sizeof(line), infile))
{
if (is_cmdline(line))
{
char* cmdline = (char*)malloc(1024);
get_cmdline(line, cmdline);
replace_string(cmdline, "@login_user@", loginuser);
replace_string(cmdline, "@abs_bindir@", bindir);
replace_string(cmdline, "@abs_srcdir@", inputdir);
replace_string(cmdline, "@abs_builddir@", outputdir);
replace_string(cmdline, "@hdfshostname@", hdfshostname);
replace_string(cmdline, "@hdfsstoreplus@", hdfsstoreplus);
replace_string(cmdline, "@hdfscfgpath@", hdfscfgpath);
replace_string(cmdline, "@hdfsport@", hdfsport);
myinfo.shell_pid[myinfo.shell_count++] = spawn_process(cmdline);
free(cmdline);
}
}
fclose(infile);
}
/*
* 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_nicely(2);
}
pgfnames_cleanup(names);
}
// Get start node SQL command
//
static char*
GetStartNodeCmdString(int i, int type)
{
int port_number;
const char* data_folder = get_node_info_name(i, type, false);
if (type == COORD)
{ port_number = myinfo.co_port[i]; }
else
{ port_number = myinfo.dn_port[i]; }
//PID_TYPE node_pid;
char* buf = (char *)malloc(MAXPGPATH * 4);
MemSet(buf, 0, MAXPGPATH * 4);
(void)snprintf(buf, MAXPGPATH * 4,
SYSTEMQUOTE "\\! \"%s/gaussdb\" %s -i -p %d -D %s/%s >>startup.log 2>&1 &" SYSTEMQUOTE,
bindir,
(type == COORD) ? "\"--coordinator\"" : "\"--datanode\"",
port_number,
temp_install,
data_folder);
return buf;
}
// Get pgxc_clean node SQL command
//
static char*
GetPgxcCleanCmdString(int i, int type)
{
int port_number;
//const char* data_folder = get_node_info_name(i, type, false);
if (type == COORD)
{ port_number = myinfo.co_port[i]; }
else
{ port_number = myinfo.dn_port[i]; }
//PID_TYPE node_pid;
char* buf = (char *)malloc(MAXPGPATH * 4);
MemSet(buf, 0, MAXPGPATH * 4);
(void)snprintf(buf, MAXPGPATH * 4,
SYSTEMQUOTE "\\! \"%s/gs_clean\" -s -v -a -p %d -r >>gs_clean_test.log 2>&1 &" SYSTEMQUOTE,
bindir,
port_number);
return buf;
}
/* Get gs_gtm node SQL command */
static char*
GetStartGtmCmdString(int i, int type)
{
const char* data_folder = get_node_info_name(i, type, false);
if (*data_folder == '\0')
return NULL;
char* buf = (char *)malloc(MAXPGPATH * 4);
MemSet(buf, 0, MAXPGPATH * 4);
(void)snprintf(buf, MAXPGPATH * 4,
SYSTEMQUOTE "\\! \"%s/gs_gtm\" -r -n gtm -D %s/%s -p %d" SYSTEMQUOTE,
bindir,
temp_install,
data_folder,
myinfo.gtm_port);
return buf;
}
/* Get gtm_ctl SQL command to stop GTM */
static char*
GetStopGtmCmdString(int i, int type)
{
const char* data_folder = get_node_info_name(i, type, false);
if (*data_folder == '\0')
return NULL;
char* buf = (char *)malloc(MAXPGPATH * 4);
MemSet(buf, 0, MAXPGPATH * 4);
(void)snprintf(buf, MAXPGPATH * 4,
SYSTEMQUOTE "\\! \"%s/gtm_ctl\" stop -Z gtm -D %s/%s -m i >>gtm.log 2>&1 &" SYSTEMQUOTE,
bindir,
temp_install,
data_folder);
return buf;
}
/*
* 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
convertSourcefilesIn(char *pcSourceSubdir, char *pcDestDir, char *pcDestSubdir, char *suffix)
{
char testtablespace[MAXPGPATH];
char testtablespace_my[MAXPGPATH];
char indir[MAXPGPATH];
struct stat st;
int iIter;
int ret;
char *datadirPath = NULL;
char **name;
char **names;
int count = 0;
bool bIisContainingDir = false;
(void)snprintf(indir, MAXPGPATH, "%s/%s", inputdir, pcSourceSubdir);
/* 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_nicely(2); }
(void)snprintf(testtablespace, MAXPGPATH, "%s/testtablespace", outputdir);
(void)snprintf(testtablespace_my, MAXPGPATH, "%s/gausstablespace", 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))
rmtree(testtablespace, true);
make_directory(testtablespace);
if (directory_exists(testtablespace_my))
rmtree(testtablespace_my, true);
make_directory(testtablespace_my);
#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 bufTemp[MAXPGPATH];
/* reject filenames not finishing in ".source" */
if (strlen(*name) < 8)
{
if (true == CheckForSubdirAndConvertSourceFiles(
srcfile,
destfile,
pcSourceSubdir,
pcDestDir,
pcDestSubdir,
indir,
*name,
suffix))
bIisContainingDir = true;
continue;
}
if (strcmp(*name + strlen(*name) - 7, ".source") != 0)
{
if (true == CheckForSubdirAndConvertSourceFiles(
srcfile,
destfile,
pcSourceSubdir,
pcDestDir,
pcDestSubdir,
indir,
*name,
suffix))
bIisContainingDir = true;
continue;
}
count++;
/* build the full actual paths to open */
(void)snprintf(prefix, strlen(*name) - 6, "%s", *name);
(void)snprintf(srcfile, MAXPGPATH, "%s/%s", indir, *name);
(void)snprintf(destfile, MAXPGPATH, "%s/%s/%s.%s", pcDestDir, pcDestSubdir,
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_nicely(2);
}
outfile = fopen(destfile, "w");
if (!outfile)
{
fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
progname, destfile, strerror(errno));
exit_nicely(2);
}
while (fgets(line, sizeof(line), infile))
{
if (!is_cmdline(line))
{
for (iIter = 0; iIter < g_stRegrReplcPatt.iNumOfPatterns; iIter++)
replace_string(line,
&g_stRegrReplcPatt.pcBuf
[g_stRegrReplcPatt.puiPatternOffset[iIter]],
&g_stRegrReplcPatt.pcBuf
[g_stRegrReplcPatt.puiPattReplValOffset[iIter]]);
replace_string(line, "@login_user@", loginuser);
replace_string(line, "@abs_srcdir@", inputdir);
replace_string(line, "@abs_builddir@", outputdir);
replace_string(line, "@abs_bindir@", psqldir);
replace_string(line, "@testtablespace@", testtablespace);
replace_string(line, "@gausstablespace@", testtablespace_my);
replace_string(line, "@libdir@", dlpath);
replace_string(line, "@DLSUFFIX@", DLSUFFIX);
replace_string(line, "@gsqldir@", psqldir);
replace_string(line, "@datanode1@", get_node_info_name(0, DATANODE, true));
replace_string(line, "@datanode2@", get_node_info_name(1, DATANODE, true));
replace_string(line, "@coordinator1@", get_node_info_name(0, COORD, true));
replace_string(line, "@coordinator2@", get_node_info_name(1, COORD, true));
char *ptr = GetStartNodeCmdString(0, DATANODE);
replace_string(line, "@start_datanode1@", ptr);
free(ptr);
ptr = GetStartNodeCmdString(1, DATANODE);
replace_string(line, "@start_datanode2@", ptr);
free(ptr);
ptr = GetStartNodeCmdString(0, COORD);
replace_string(line, "@start_coordinator1@", ptr);
free(ptr);
ptr = GetStartNodeCmdString(1, COORD);
replace_string(line, "@start_coordinator2@", ptr);
free(ptr);
ptr = GetStopGtmCmdString(0,GTM);
replace_string(line, "@stop_gtm@", ptr);
free(ptr);
ptr = GetStartGtmCmdString(0,GTM);
replace_string(line, "@start_gtm@", ptr);
free(ptr);
ptr = GetPgxcCleanCmdString(0, COORD);
replace_string(line, "@start_pgxc_clean@", ptr);
free(ptr);
char s[16];
sprintf(s, "%d", get_port_number(0, COORD));
replace_string(line, "@portstring@", s);
sprintf(s, "%d", get_port_number(1, COORD));
replace_string(line, "@cn2_portstring@", s);
if (temp_install)
{
char tmpdatadir[1024];
(void)snprintf(tmpdatadir,MAXPGPATH, "\"%s\"", temp_install);
replace_string(line, "@cndata@", tmpdatadir);
(void)snprintf(bufTemp, MAXPGPATH, "\"%s/data\"", temp_install);
replace_string(line, "@datadir@", bufTemp);
(void)snprintf(bufTemp, MAXPGPATH, "\"%s/data1\"", temp_install);
replace_string(line, "@data1dir@", bufTemp);
(void)snprintf(bufTemp, MAXPGPATH, "-p%u",
0xC000 | (PG_VERSION_NUM & 0x3FFF));
replace_string(line, "@psqlport@", bufTemp);
}
else
{
datadirPath = getenv("GAUSSDATA");
if (datadirPath)
{
(void)snprintf(bufTemp, MAXPGPATH, "\"%s\"", datadirPath);
replace_string(line, "@datadir@", bufTemp);
}
else
replace_string(line, "@datadir@", ".");
datadirPath = getenv("GAUSSDATA1");
if (datadirPath)
{
(void)snprintf(bufTemp, MAXPGPATH, "\"%s\"", datadirPath);
replace_string(line, "@data1dir@", bufTemp);
}
else
replace_string(line, "@data1dir@", ".");
replace_string(line, "@psqlport@", "");
}
replace_string(line, "@hdfshostname@", hdfshostname);
replace_string(line, "@hdfsstoreplus@", hdfsstoreplus);
replace_string(line, "@hdfscfgpath@", hdfscfgpath);
replace_string(line, "@hdfsport@", hdfsport);
sprintf(s, "%d", get_port_number(0, DATANODE));
replace_string(line, "@dn1port@", s);
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 && !bIisContainingDir)
{
fprintf(stderr, _("%s: no *.source files found in \"%s\"\n"),
progname, indir);
exit_nicely(2);
}
pgfnames_cleanup(names);
}
/* Create the .sql and .out files from the .source files, if any */
static void
convert_sourcefiles(void)
{
convertSourcefilesIn("input", inputdir, "sql", "sql");
convertSourcefilesIn("output", outputdir, "expected", "out");
// Convert source file to SQL file according some rules
//
convertSourcefilesIn(TwoPhaseTxntTestDirInput,inputdir, "sql", "sql");
convertSourcefilesIn(TwoPhaseTxntTestDirOutput,outputdir, "expected", "out");
if (g_stRegrReplcPatt.puiPatternOffset != NULL)
REGR_FREE(g_stRegrReplcPatt.puiPatternOffset);
}
/*
* Scan resultmap file to find which platform-specific expected files to use.
*
* The format of each line of the file is
* testname/hostplatformpattern=substitutefile
* where the hostplatformpattern is evaluated per the rules of expr(1),
* namely, it is a standard regular expression with an implicit ^ at the start.
* (We currently support only a very limited subset of regular expressions,
* see string_matches_pattern() above.) What hostplatformpattern will be
* matched against is the config.guess output. (In the shell-script version,
* we also provided an indication of whether gcc or another compiler was in
* use, but that facility isn't used anymore.)
*/
static void
load_resultmap(void)
{
char buf[MAXPGPATH];
FILE* f;
/* scan the file ... */
(void)snprintf(buf, sizeof(buf), "%s/resultmap", inputdir);
f = fopen(buf, "r");
if (!f)
{
/* OK if it doesn't exist, else complain */
if (errno == ENOENT)
{ return; }
fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
progname, buf, strerror(errno));
exit_nicely(2);
}
while (fgets(buf, sizeof(buf), f))
{
char* platform;
char* file_type;
char* expected;
int i;
/* strip trailing whitespace, especially the newline */
i = strlen(buf);
while (i > 0 && isspace((unsigned char) buf[i - 1]))
{ buf[--i] = '\0'; }
/* parse out the line fields */
file_type = strchr(buf, ':');
if (!file_type)
{
fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
buf);
exit_nicely(2);
}
*file_type++ = '\0';
platform = strchr(file_type, ':');
if (!platform)
{
fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
buf);
exit_nicely(2);
}
*platform++ = '\0';
expected = strchr(platform, '=');
if (!expected)
{
fprintf(stderr, _("incorrectly formatted resultmap entry: %s\n"),
buf);
exit_nicely(2);
}
*expected++ = '\0';
/*
* if it's for current platform, save it in resultmap list. Note: by
* adding at the front of the list, we ensure that in ambiguous cases,
* the last match in the resultmap file is used. This mimics the
* behavior of the old shell script.
*/
if (string_matches_pattern(host_platform, platform))
{
_resultmap* entry = (_resultmap*)malloc(sizeof(_resultmap));
entry->test = strdup(buf);
entry->type = strdup(file_type);
entry->resultfile = strdup(expected);
entry->next = resultmap;
resultmap = entry;
}
}
fclose(f);
}
/*
* Check in resultmap if we should be looking at a different file
*/
static
const char*
get_expectfile(const char* testname, const char* file)
{
char* file_type;
_resultmap* rm;
/*
* Determine the file type from the file name. This is just what is
* following the last dot in the file name.
*/
if (!file || !(file_type = strrchr((char*)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;
}
#ifndef PGXC
/*
* Handy subroutine for setting an environment variable "var" to "val"
*/
static void
doputenv(const char* var, const char* val)
{
char* s = (char*)malloc(strlen(var) + strlen(val) + 2);
sprintf(s, "%s=%s", var, val);
putenv(s);
}
#endif
/*
* Set the environment variable "pathname", prepending "addval" to its
* old value (if any).
*/
/*lint -e429*/
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 = (char*)malloc(strlen(pathname) + strlen(addval) + 2);
sprintf(newval, "%s=%s", pathname, addval);
}
else
{
newval = (char*)malloc(strlen(pathname) + strlen(addval) + strlen(oldval) + 3);
sprintf(newval, "%s=%s%c%s", pathname, addval, separator, oldval);
}
(void)putenv(newval);
}
/*lint +e429*/
/*
* Prepare environment variables for running regression tests
*/
static void
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
}
/*
* 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");
(void)putenv("LC_MESSAGES=C");
/* test for a bug of psqlrc contain ~ */
(void)putenv("PSQLRC='~/x'");
/*
* Set encoding as requested
*/
if (encoding)
{ doputenv("PGCLIENTENCODING", encoding); }
else
{ unsetenv("PGCLIENTENCODING"); }
/*
* Set timezone and datestyle for datetime-related tests
*/
(void)putenv("PGTZ=PST8PDT");
(void)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.
*/
/*lint -e429*/
{
const char* my_pgoptions = "-c intervalstyle=postgres_verbose";
const char* old_pgoptions = getenv("PGOPTIONS");
char* new_pgoptions;
if (!old_pgoptions)
{ old_pgoptions = ""; }
new_pgoptions = (char*)malloc(strlen(old_pgoptions) + strlen(my_pgoptions) + 12);
(void)sprintf(new_pgoptions, "PGOPTIONS=%s %s", old_pgoptions, my_pgoptions);
(void)putenv(new_pgoptions);
}
/*lint +e429*/
if (temp_install)
{
/*
* Clear out any environment vars that might cause psql to connect to
* the wrong postmaster, or otherwise behave in nondefault ways. (Note
* we also use psql's -X switch consistently, so that ~/.psqlrc files
* won't mess things up.) Also, set PGPORT to the temp port, and set
* or unset PGHOST depending on whether we are using TCP or Unix
* sockets.
*/
unsetenv("PGDATABASE");
unsetenv("PGUSER");
unsetenv("PGSERVICE");
unsetenv("PGSSLMODE");
unsetenv("PGREQUIRESSL");
unsetenv("PGCONNECT_TIMEOUT");
unsetenv("PGDATA");
if (hostname != NULL)
{ doputenv("PGHOST", hostname); }
else
{ unsetenv("PGHOST"); }
unsetenv("PGHOSTADDR");
if (port != -1)
{
char s[16];
sprintf(s, "%d", port);
doputenv("PGPORT", s);
}
/*
* Adjust path variables to point into the temp-install tree
*/
tmp = (char*)malloc(strlen(temp_install) + 32 + strlen(bindir));
sprintf(tmp, "%s/install/%s", temp_install, bindir);
bindir = tmp;
tmp = (char*)malloc(strlen(temp_install) + 32 + strlen(libdir));
sprintf(tmp, "%s/install/%s", temp_install, libdir);
libdir = tmp;
tmp = (char*)malloc(strlen(temp_install) + 32 + strlen(datadir));
sprintf(tmp, "%s/install/%s", temp_install, datadir);
datadir = tmp;
/*char* gtm = "datagtm";
tmp = (char*)malloc(strlen(temp_install) + 32 + strlen(gtm));
sprintf(tmp, "%s/datagtm", temp_install);
temp_gtm_install = tmp;*/
/* 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
{
const char* pghost;
const char* pgport;
/*
* When testing an existing install, we honor existing environment
* variables, except if they're overridden by command line options.
*/
if (hostname != NULL)
{
doputenv("PGHOST", hostname);
unsetenv("PGHOSTADDR");
}
if (port != -1)
{
char s[16];
sprintf(s, "%d", port);
doputenv("PGPORT", s);
}
if (user != NULL)
{ doputenv("PGUSER", user); }
/*
* 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();
}
/*
* Spawn a process to execute the given shell command; don't wait for it
*
* Returns the process ID (or HANDLE) so we can wait for it later
*/
PID_TYPE
spawn_process(const char* cmdline)
{
#ifndef WIN32
pid_t pid;
/*
* Must flush I/O buffers before fork. Ideally we'd use fflush(NULL) here
* ... does anyone still care about systems where that doesn't work?
*/
(void)fflush(stdout);
(void)fflush(stderr);
if (logfile)
{ (void)fflush(logfile); }
pid = fork();
if (pid == -1)
{
fprintf(stderr, _("%s: could not fork: %s\n"),
progname, strerror(errno));
exit_nicely(2);
}
/*lint -e429*/
if (pid == 0)
{
/*
* In child
*
* Instead of using system(), exec the shell directly, and tell it to
* "exec" the command too. This saves two useless processes per
* parallel test case.
*/
char* cmdline2 = (char*)malloc(strlen(cmdline) + 6);
sprintf(cmdline2, "exec %s", cmdline);
(void)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... */
}
/*lint +e429*/
/* in parent */
return pid;
#else
char* cmdline2;
BOOL b;
STARTUPINFO si;
PROCESS_INFORMATION pi;
HANDLE origToken;
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_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: error code %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);
}
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_nicely(2);
}
cmdline2 = malloc(strlen(cmdline) + 8);
sprintf(cmdline2, "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\": %lu\n"),
cmdline2, GetLastError());
exit_nicely(2);
}
free(cmdline2);
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
return pi.hProcess;
#endif
}
/* start a script execution process for specified file and return process ID */
static PID_TYPE
scriptExecute(const char *scriptname,
_stringlist ** resultfiles,
_stringlist ** expectfiles,
_stringlist ** tags)
{
PID_TYPE pid;
char infile[MAXPGPATH];
char outfile[MAXPGPATH];
char expectfile[MAXPGPATH];
char scriptCmd[MAXPGPATH * 3];
size_t offset = 0;
/*
* Look for files in the output dir first, consistent with a vpath search.
* This is mainly to create more reasonable error messages if the file is
* not found. It also allows local test overrides when running pg_regress
* outside of the source tree.
*/
(void)snprintf(infile, sizeof(infile), "%s/scripts/%s", outputdir, scriptname);
if (!file_exists(infile))
(void)snprintf(infile, sizeof(infile), "%s/scripts/%s", inputdir, scriptname);
(void)snprintf(outfile, sizeof(outfile), "%s/results/%s.out",
outputdir, scriptname);
(void)snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out",
outputdir, scriptname);
if (!file_exists(expectfile))
(void)snprintf(expectfile, sizeof(expectfile), "%s/expected/%s.out",
inputdir, scriptname);
add_stringlist_item(resultfiles, outfile);
add_stringlist_item(expectfiles, expectfile);
if (launcher)
offset += snprintf(scriptCmd + offset, sizeof(scriptCmd) - offset,
"%s ", launcher);
(void)snprintf(scriptCmd + offset, sizeof(scriptCmd) - offset,
SYSTEMQUOTE "\"%s\" > \"%s\" 2>&1" SYSTEMQUOTE, infile, outfile);
pid = spawn_process(scriptCmd);
if (pid == INVALID_PID)
{
fprintf(stderr, _("could not start process for test %s\n"), scriptname);
exit(2);
}
return pid;
}
/*
* Count bytes in file
*/
static long
file_size(const char* file)
{
long r;
FILE* f = fopen(file, "r");
if (!f)
{
fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
progname, file, strerror(errno));
return -1;
}
fseek(f, 0, SEEK_END);
r = ftell(f);
fclose(f);
return r;
}
/*
* Count lines in file
*/
static int
file_line_count(const char* file)
{
int c;
int l = 0;
FILE* f = fopen(file, "r");
if (!f)
{
fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
progname, file, strerror(errno));
return -1;
}
while ((c = fgetc(f)) != EOF)
{
if (c == '\n')
{ l++; }
}
fclose(f);
return l;
}
bool
file_exists(const char* file)
{
FILE* f = fopen(file, "r");
if (!f)
{ return false; }
fclose(f);
return true;
}
static bool
directory_exists(const char* dir)
{
struct stat st;
if (stat(dir, &st) != 0)
return false;
if (S_ISDIR(st.st_mode))
return true;
return false;
}
/* Create nested directory structure */
static void
makeNestedDirectory(const char *results_file_path)
{
char *pcOutFileLastSep = NULL;
char outfile[MAXPGPATH];
int iSepCount = 0;
char cSeparator;
(void)snprintf(outfile, sizeof(outfile), "%s/results/%s.out", outputdir,
results_file_path);
do
{
pcOutFileLastSep = strrchr(outfile, '/');
if (NULL == pcOutFileLastSep)
pcOutFileLastSep = strrchr(outfile, '\\');
if (NULL != pcOutFileLastSep)
{
cSeparator = *pcOutFileLastSep;
*pcOutFileLastSep = '\0';
iSepCount++;
if (directory_exists(outfile))
{
do
{
*pcOutFileLastSep = cSeparator;
iSepCount--;
if ((iSepCount > 0) && (!directory_exists(outfile)))
{
make_directory(outfile);
pcOutFileLastSep = outfile + strlen(outfile);
}
} while (iSepCount > 0);
}
}
} while (iSepCount > 0);
}
/* 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_nicely(2);
}
}
/*
* In: filename.ext, Return: filename_i.ext, where 0 < i <= 9
*/
static char*
get_alternative_expectfile(const char* expectfile, int i)
{
char* last_dot;
int ssize = strlen(expectfile) + 2 + 1;
char* tmp = (char*) 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';
(void)snprintf(s, ssize, "%s_%d.%s", tmp, i, last_dot + 1);
free(tmp);
return s;
}
/*
* Run a "diff" command and also check that it didn't crash
*/
static int
run_diff(const char* cmd, const char* filename)
{
int r;
r = system(cmd);
if (!WIFEXITED(r) || WEXITSTATUS(r) > 1)
{
fprintf(stderr, _("diff command failed with status %d: %s\n"), r, cmd);
exit_nicely(2);
}
#ifdef WIN32
/*
* On WIN32, if the 'diff' command cannot be found, system() returns 1,
* but produces nothing to stdout, so we check for that here.
*/
if (WEXITSTATUS(r) == 1 && file_size(filename) <= 0)
{
fprintf(stderr, _("diff command not found: %s\n"), cmd);
exit_nicely(2);
}
#endif
return WEXITSTATUS(r);
}
/*
* Check the actual result file for the given test against expected results
*
* Returns true if different (failure), false if correct match found.
* In the true case, the diff is appended to the diffs file.
*/
static bool
results_differ(const char* testname, const char* resultsfile, const char* default_expectfile)
{
char expectfile[MAXPGPATH];
char smartmatchfile[MAXPGPATH];
char diff[MAXPGPATH];
char smartmatch[MAXPGPATH];
char cmd[MAXPGPATH * 3];
char acBestExpectFile[MAXPGPATH];
FILE *difffile;
int best_line_count;
int i;
int l;
const char *platform_expectfile;
int result;
char *pcPrettyDiffFile;
/*
* We can pass either the resultsfile or the expectfile, they should have
* the same type (filename.type) anyway.
*/
platform_expectfile = get_expectfile(testname, resultsfile);
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, '/');
if (p)
{ strcpy(++p, platform_expectfile); }
}
/* Name to use for temporary diff file */
(void)snprintf(diff, sizeof(diff), "%s.diff", resultsfile);
/* OK, run the diff */
(void)snprintf(cmd, sizeof(cmd),
SYSTEMQUOTE "diff %s \"%s\" \"%s\" > \"%s\" 2>&1" SYSTEMQUOTE,
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(acBestExpectFile, expectfile);
for (i = 0; i <= 9; i++)
{
char* alt_expectfile;
alt_expectfile = get_alternative_expectfile(expectfile, i);
if (!file_exists(alt_expectfile))
{ continue; }
(void)snprintf(cmd, sizeof(cmd),
SYSTEMQUOTE "diff %s \"%s\" \"%s\" > \"%s\"" SYSTEMQUOTE,
basic_diff_opts, alt_expectfile, resultsfile, diff);
if (run_diff(cmd, diff) == 0)
{
unlink(diff);
return false;
}
l = file_line_count(diff);
if (l < best_line_count)
{
/* This diff was a better match than the last one */
best_line_count = l;
strcpy(acBestExpectFile, alt_expectfile);
}
free(alt_expectfile);
}
/*
* fall back on the canonical results file if we haven't tried it yet and
* haven't found a complete match yet.
*/
if (platform_expectfile)
{
(void)snprintf(cmd, sizeof(cmd),
SYSTEMQUOTE "diff %s \"%s\" \"%s\" > \"%s\"" SYSTEMQUOTE,
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;
strcpy(acBestExpectFile, default_expectfile);
}
}
/* framing smartmatch.pl script complete file name */
(void)snprintf(smartmatch, sizeof(smartmatch), "%s/%s",
outputdir, "smartmatch.pl");
if (!file_exists(smartmatch))
(void)snprintf(smartmatch, sizeof(smartmatch), "%s/%s", inputdir,
"smartmatch.pl");
if (file_exists(smartmatch))
{
/* Name to use for temporary diff file */
(void)snprintf(smartmatchfile, sizeof(smartmatchfile), "%s.smartmatch", acBestExpectFile);
/*
* Is the unordered result set is leading to test failure? check and
* verify the unordered result set as if test specifies that the result
* can vary with comments of unordered.
*/
(void)snprintf(cmd, sizeof(cmd),
SYSTEMQUOTE "%s \"%s\" \"%s\" \"%s\" " SYSTEMQUOTE,
smartmatch, default_expectfile, resultsfile, smartmatchfile);
result = system(cmd);
if (WEXITSTATUS(result) == 1)
{
/* In a success of smartmatch, there is no need of smartmatch file */
unlink(smartmatchfile);
unlink(diff);
return false;
}
else if (WEXITSTATUS(result) == 2)
{
unlink(smartmatchfile);
pcPrettyDiffFile = acBestExpectFile;
}
else
pcPrettyDiffFile = smartmatchfile;
}
else
pcPrettyDiffFile = acBestExpectFile;
/*
* Use the best comparison file to generate the "pretty" diff, which we
* append to the diffs summary file.
*/
(void)snprintf(cmd, sizeof(cmd),
SYSTEMQUOTE "diff %s \"%s\" \"%s\" >> \"%s\" 2>&1" SYSTEMQUOTE,
pretty_diff_opts, pcPrettyDiffFile, resultsfile, difffilename);
(void)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)
{
int tests_left;
int i;
#ifdef WIN32
PID_TYPE* active_pids = malloc(num_tests * sizeof(PID_TYPE));
memcpy(active_pids, pids, num_tests * sizeof(PID_TYPE));
#endif
if (g_bEnablePerfDataPrint)
{
memset(&g_stResourceUsageDetails,
0, sizeof(REGR_AVG_RSRCE_USAGE_STRU));
}
tests_left = num_tests;
while (tests_left > 0)
{
PID_TYPE p;
#ifndef WIN32
int exit_status;
unsigned int uiPidChanged;
if (false == g_bEnablePerfDataPrint)
{
p = wait(&exit_status);
if (p == INVALID_PID)
{
fprintf(stderr, _("failed to wait for subprocesses: %s\n"),
gs_strerror(errno));
exit(2);
}
}
else
{
if (REGR_SUCCESS
!= regrGetRsrcUsageWithRepeatOnFail(&g_stPrevCpuUsage,
&uiPidChanged))
{
fprintf(stderr, _("Could not get the CPU Usage info! \n"));
g_stPrevCpuUsage.ulCpuTotalTime = 0;
}
do
{
p = waitpid(REGR_PID_WAIT_FOR_ANY_CHILD, &exit_status, WNOHANG);
if (p == INVALID_PID)
{
fprintf(stderr, _("failed to wait for subprocesses: %s\n"),
gs_strerror(errno));
exit(2);
}
if (NO_CHILD_PROC_HAS_CHANGED_STATE != p)
{
/* Getting subsequent CpuMemUsageReading to get usage in %*/
regrGetSubsequentUsage();
break;
}
pg_usleep(REGR_DELAY_FOR_PERF_COLLECTION);
/* Getting the subsequent CPU / Memory usage details */
regrGetSubsequentUsage();
} while (true);
}
#else
DWORD exit_status;
int r;
r = WaitForMultipleObjects(tests_left, active_pids, FALSE, INFINITE);
if (r < WAIT_OBJECT_0 || r >= WAIT_OBJECT_0 + tests_left)
{
fprintf(stderr, _("failed to wait for subprocesses: %lu\n"),
GetLastError());
exit_nicely(2);
}
p = active_pids[r - WAIT_OBJECT_0];
/* compact the active_pids array */
active_pids[r - WAIT_OBJECT_0] = active_pids[tests_left - 1];
#endif /* WIN32 */
for (i = 0; i < num_tests; i++)
{
if (p == pids[i])
{
#ifdef WIN32
GetExitCodeProcess(pids[i], &exit_status);
CloseHandle(pids[i]);
#endif
pids[i] = INVALID_PID;
statuses[i] = (int) exit_status;
if (names)
{ status(" %s", names[i]); }
tests_left--;
REGR_STOP_TIMER_TEMP(i);
break;
}
}
}
if ((g_bEnablePerfDataPrint) && (0 != g_stResourceUsageDetails.ulCount))
{
/* Average System CPU Usage */
g_stResourceUsageDetails.dSAvgcpuUsage
/= g_stResourceUsageDetails.ulCount;
/* Average User CPU usage */
g_stResourceUsageDetails.dUAvgcpuUsage
/= g_stResourceUsageDetails.ulCount;
/* Average Memory Usage in MBs and Converting the MB value into Bytes */
g_stResourceUsageDetails.dAvgMemUsage
= (g_stResourceUsageDetails.dAvgMemUsage
/ g_stResourceUsageDetails.ulCount) * REGR_MCR_SIZE_1MB;
regrConvertSizeInBytesToReadableForm(
(unsigned long)g_stResourceUsageDetails.dAvgMemUsage,
(char*)g_stResourceUsageDetails.acMemUsage,
(unsigned int)MAX_REGR_TOKEN_LEN);
/* Average Memory Usage in percentage */
g_stResourceUsageDetails.dAvgMemUsagePct
/= g_stResourceUsageDetails.ulCount;
}
#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);
}
/* Allocates a block of memory which is required to execute run_schedule().
*
* When the function is called for the first time, i.e. iOldSize is 0 and
* *pppcMemBlockStart is NULL:
* 1. A fresh block of memory is allocated and the pointer for holding the
* 'start of the memory block' is updated. (pppcMemBlockStart is the address
* of the pointer holding memory block)
* 2. Various other pointers, in the caller function (like resultfiles
* expectfiles etc), which are supposed to point to the different parts of
* this memory block, are updated.
*
* When the initial block of memory is exhausted, then this function is called
* subsequently to:
* 1. Backup the old memory block address
* 2. Get a fresh block of memory based on the value: iReqSize
* 3. Various other pointers, in the caller function (like resultfiles
* expectfiles etc), which are pointing to the different parts of the old
* memory block, are updated to point to the appropriate area in the new
* memory block
* 4. Copy the contents of the old block (based on the iOldSize) into the
* appropriate area of the new block.
*
* Returns 0 in case of success and REGR_ERRCODE_MALLOC_FAILURE on memory
* allocation failure
*/
static int
regrMallocForTest(int iReqSize,
int iOldSize,
char ***pppcMemBlockStart,
_stringlist ***pppstResultfiles,
_stringlist ***pppstExpectfiles,
_stringlist ***pppstTags,
PID_TYPE** ppPids,
int** ppiStatuses)
{
char** ppcOldMemBlockStart = NULL;
char* pcTmp;
/* When the function is called for the subsequent times, *pppcMemBlockStart
* will be pointing to the old memory block. This address need to be
* backuped, so that the contents can be copied into the newly allocating
* block */
if (*pppcMemBlockStart != NULL)
ppcOldMemBlockStart = *pppcMemBlockStart;
/* Allocating memory for test file path storage, result file path storage,
* expected file path storage, tags (not used as of now) storage, PID TYPE
* storage and the status storage for the scheduler test execution
*
* Memory Layout:
* +-----------+-----------+-----------+-----------+-----------+----------+
* | Test | Result | Expected | Tags | PID | Statuses |
* | file | file | file | | TYPE | |
* | path | path | path | storage | storage | storage |
* | storage | storage | storage | | | |
* +-----------+-----------+-----------+-----------+-----------+----------+
*/
*pppcMemBlockStart
= (char **)malloc((sizeof(char *)
+ sizeof(_stringlist *)
+ sizeof(_stringlist *)
+ sizeof(_stringlist *)
+ sizeof(PID_TYPE)
+ sizeof(int)) * iReqSize);
if (*pppcMemBlockStart == NULL)
{
fprintf(stderr, _("Could not allocate memory for schedule test "
"execution. Requested size: %d.\n"),
(int)((sizeof(char *)
+ sizeof(_stringlist *)
+ sizeof(_stringlist *)
+ sizeof(_stringlist *)
+ sizeof(PID_TYPE)
+ sizeof(int)) * iReqSize));
*pppcMemBlockStart = ppcOldMemBlockStart;
return REGR_ERRCODE_MALLOC_FAILURE;
}
/* Updating the output parameter to point to the result file storage area
* in the allocated memory block */
*pppstResultfiles
= (_stringlist **)(void*)((char*)(void*)(*pppcMemBlockStart)
+ (sizeof(char *) * iReqSize));
/* Updating the output parameter to point to the expected file storage area
* in the allocated memory block */
*pppstExpectfiles
= (_stringlist **)(void*)((char*)(void*)(*pppstResultfiles)
+ (sizeof(_stringlist *) * iReqSize));
/* Updating the output parameter to point to the Tags storage area
* in the allocated memory block */
*pppstTags
= (_stringlist **)(void*)((char*)(void*)(*pppstExpectfiles)
+ (sizeof(_stringlist *) * iReqSize));
/* Updating the output parameter to point to the PID TYPE storage area
* in the allocated memory block */
*ppPids = (PID_TYPE*)(void*)((char*)(void*)(*pppstTags)
+ (sizeof(_stringlist *) * iReqSize));
/* Updating the output parameter to point to the STATUS storage area
* in the allocated memory block */
*ppiStatuses = (int*)(void*)((char*)(void*)(*ppPids)
+ (sizeof(PID_TYPE) * iReqSize));
/* Resetting the Result file, expected file and tags sections of the
* allocated memory block */
memset(*pppstResultfiles, 0, sizeof(_stringlist *) * iReqSize);
memset(*pppstExpectfiles, 0, sizeof(_stringlist *) * iReqSize);
memset(*pppstTags, 0, sizeof(_stringlist *) * iReqSize);
/* Case of Reallocating the memory. So copy the data from the old memory
* into the freshly allocated block
*/
if ((iOldSize != 0) && (ppcOldMemBlockStart != NULL))
{
/* Copying test file path pointers */
pcTmp = (char*)(void*)ppcOldMemBlockStart;
memcpy(*pppcMemBlockStart, pcTmp, (sizeof(char *) * iOldSize));
/* Copying result file path pointers */
pcTmp = pcTmp + (sizeof(char *) * iOldSize);
memcpy(*pppstResultfiles, pcTmp, (sizeof(_stringlist *) * iOldSize));
/* Copying expected file path pointers */
pcTmp = pcTmp + (sizeof(_stringlist *) * iOldSize);
memcpy(*pppstExpectfiles, pcTmp, (sizeof(_stringlist *) * iOldSize));
/* Copying tags pointers */
pcTmp = pcTmp + (sizeof(_stringlist *) * iOldSize);
memcpy(*pppstTags, pcTmp, (sizeof(_stringlist *) * iOldSize));
/* Copying PID TYPES */
pcTmp = pcTmp + (sizeof(_stringlist *) * iOldSize);
memcpy(*ppPids, pcTmp, (sizeof(PID_TYPE) * iOldSize));
/* Copying statuses */
pcTmp = pcTmp + (sizeof(PID_TYPE) * iOldSize);
memcpy(*ppiStatuses, pcTmp, (sizeof(int) * iOldSize));
/* Freeing the old memory block */
free(ppcOldMemBlockStart);
}
return REGR_SUCCESS;
}
/*
* Get the time difference between the start and end time of the test execution.
* Also updates the total time taken by the entire test suite.
*/
static void
regrGetElapsedTime(void)
{
g_dTotalTime = (g_stRegrStopTime.tv_sec - g_stRegrStartTime.tv_sec) +
(g_stRegrStopTime.tv_usec - g_stRegrStartTime.tv_usec)
* 0.000001;
g_dGroupTotalTime = g_dGroupTotalTime + g_dTotalTime;
}
/*
* Parse the line buffer read from the schedule file and populate the test
* details
*/
static int regrParseLineBuffer(char *pcLineBuf,
char **ppcLastSpace,
int *piNumTests,
int *piMaxParallelTests,
char ***pppctests,
_stringlist ***pppstResultfiles,
_stringlist ***pppstExpectfiles,
_stringlist ***pppstTags,
PID_TYPE **ppPids,
int **ppiStatuses)
{
int iRet = REGR_SUCCESS;
char* pcTemp = NULL;
bool bInsideWord = false;
*ppcLastSpace = NULL;
for (pcTemp = pcLineBuf; *pcTemp; pcTemp++)
{
if (isspace((unsigned char) *pcTemp))
{
*pcTemp = '\0';
bInsideWord = false;
*ppcLastSpace = pcTemp;
}
else if (!bInsideWord)
{
if (*piNumTests >= *piMaxParallelTests)
{
/* Pre-allocated memory got exhausted. So extend it by
* reallocating and copying data from old block to the new
* block. Also make sure that the pointer are pointing to
* the appropriate positions in the new block */
iRet = regrMallocForTest(*piMaxParallelTests
+ REGR_MAX_PARALLEL_TESTS,
*piMaxParallelTests,
pppctests,
pppstResultfiles,
pppstExpectfiles,
pppstTags,
ppPids,
ppiStatuses);
if (iRet != REGR_SUCCESS)
{
return iRet;
}
/* Memory has been extented for an additional
* REGR_MAX_PARALLEL_TESTS */
*piMaxParallelTests += REGR_MAX_PARALLEL_TESTS;
}
(*pppctests)[*piNumTests] = pcTemp;
(*piNumTests)++;
bInsideWord = true;
}
}
return REGR_SUCCESS;
}
/*
* Reload line buffer and pasrse the line read from the schedule file
*/
static int regrReloadAndParseLineBuffer(bool *pbBuffReloadReq,
bool *pbHalfReadTest,
char **ppcLastSpace,
FILE *scf,
int *piNumTests,
int *piMaxParallelTests,
char ***pppctests,
_stringlist ***pppstResultfiles,
_stringlist ***pppstExpectfiles,
_stringlist ***pppstTags,
PID_TYPE **ppPids,
int **ppiStatuses)
{ int iRet = REGR_SUCCESS;
int j = 0;
char* pcLineBuf = NULL;
char* pcTemp = NULL;
g_uiCurLineBufIdx++;
if (g_uiCurLineBufIdx == REGR_MAX_NUM_OF_LINE_BUFF)
{
fprintf(stderr, _("\n Length of the line in schedule file is too long. "
"Maximum supported length is %d.\n"),
(REGR_MAX_NUM_OF_LINE_BUFF * REGR_MAX_SCH_LINE_SIZE));
return REGR_ERRCODE_MAX_NUM_OF_LINE_BUFF;
}
/* g_uiCurLineBufIdx is the index to the buffer and g_uiTotalBuf is the
* total number of buffers that are allocated */
if (g_uiCurLineBufIdx == g_uiTotalBuf)
{
g_ppcBuf[g_uiCurLineBufIdx] = (char*)malloc(REGR_MAX_SCH_LINE_SIZE);
if (g_ppcBuf[g_uiCurLineBufIdx] == NULL)
{
fprintf(stderr, _("Could not allocate memory for reading line from "
"schedule file. Requested size: %d.\n"),
REGR_MAX_SCH_LINE_SIZE);
return REGR_ERRCODE_MALLOC_FAILURE;
}
g_uiTotalBuf++;
}
pcLineBuf = g_ppcBuf[g_uiCurLineBufIdx];
memset(pcLineBuf, 0, REGR_MAX_SCH_LINE_SIZE);
if (true == *pbHalfReadTest)
{
/* Copy the 'half read' test file detail into the new buffer's start */
for (j = 0, pcTemp = *ppcLastSpace + 1; *pcTemp != '\0'; pcTemp++, j++)
pcLineBuf[j] = *pcTemp;
*pbHalfReadTest = false;
}
/* Loading the line buffer with the 'left out' characters of the current
* line in the schedule file */
if (NULL == fgets(&(pcLineBuf[j]), REGR_MAX_SCH_LINE_SIZE - j, scf))
return REGR_EOF_REACHED;
j = strlen(pcLineBuf);
if ((j > 0) && ('\n' != pcLineBuf[j - 1]) && (!feof(scf)))
*pbBuffReloadReq = true;
/* Parse the Line buffer to get the test file details */
iRet = regrParseLineBuffer(pcLineBuf,
ppcLastSpace,
piNumTests,
piMaxParallelTests,
pppctests,
pppstResultfiles,
pppstExpectfiles,
pppstTags,
ppPids,
ppiStatuses);
if (iRet != REGR_SUCCESS)
return iRet;
if ((true == *pbBuffReloadReq)
&& (NULL != *ppcLastSpace)
&& (0 != strlen(*ppcLastSpace + 1)))
{
*pbHalfReadTest = true;
(*piNumTests)--;
}
return iRet;
}
/*
* Run all the tests specified in one schedule file
*/
static void
run_schedule(const char *schedule, test_function tfunc, diag_function dfunc)
{
char **tests = NULL;
_stringlist **resultfiles;
_stringlist **expectfiles;
_stringlist **tags;
PID_TYPE *pids;
int *statuses;
int i;
int iRet = REGR_SUCCESS;
int iMaxParallelTests = REGR_MAX_PARALLEL_TESTS;
_stringlist *ignorelist = NULL;
FILE *scf;
int line_num = 0;
bool bRhsSpacePresent = false;
bool bscript = false;
bool bIgnoreLineOnReload = false;
/* Allocating memory for holding the addresses of all line buffers.
* Each line buffer is used to hold a part (or full) of the line, read from
* the scheduler file */
g_ppcBuf = (char**)malloc(sizeof(char*) * REGR_MAX_NUM_OF_LINE_BUFF);
if (NULL == g_ppcBuf)
{
fprintf(stderr, _("Could not allocate memory for buffer to hold line "
"buffer addresses. Requested size: %lu.\n"),
(REGR_MAX_NUM_OF_LINE_BUFF * sizeof(char*)));
exit(2);
}
/* Allocating memory for holding a line to be read from the schedule file */
g_ppcBuf[g_uiCurLineBufIdx] = (char*)malloc(REGR_MAX_SCH_LINE_SIZE);
if (g_ppcBuf[g_uiCurLineBufIdx] == NULL)
{
fprintf(stderr, _("Could not allocate memory for reading line from "
"schedule file. Requested size: %d.\n"),
REGR_MAX_SCH_LINE_SIZE);
iRet = REGR_ERRCODE_MALLOC_FAILURE;
goto LB_ERR_HNDL_LVL_1;
}
g_uiTotalBuf++;
memset(g_ppcBuf[g_uiCurLineBufIdx], 0, REGR_MAX_SCH_LINE_SIZE);
/* Allocating memory for test file path storage, result file path storage,
* expected file path storage, tags (not used as of now) storage, PID TYPE
* storage and the status storage for the scheduler test execution */
iRet = regrMallocForTest(iMaxParallelTests,
0,
&tests,
&resultfiles,
&expectfiles,
&tags,
&pids,
&statuses);
if (iRet != REGR_SUCCESS)
{
iRet = REGR_ERRCODE_MALLOC_FAILURE;
goto LB_ERR_HNDL_LVL_2;
}
/* Initializing the "total time taken by the test suite execution" */
g_dGroupTotalTime = 0;
scf = fopen(schedule, "r");
if (!scf)
{
fprintf(stderr, _("%s: could not open file \"%s\" for reading: %s\n"),
progname, schedule, gs_strerror(errno));
iRet = REGR_ERRCODE_FOPEN_FAILURE;
goto LB_ERR_HNDL_LVL_3;
}
while (fgets(g_ppcBuf[g_uiCurLineBufIdx], REGR_MAX_SCH_LINE_SIZE, scf))
{
char *test = NULL;
char *pcLastSpace = NULL;
char *c;
int num_tests;
bool bBuffReloadReq = false;
bool bHalfReadTest = false;
i = strlen(g_ppcBuf[g_uiCurLineBufIdx]);
if ((i > 0)
&& ('\n' != g_ppcBuf[g_uiCurLineBufIdx][i - 1])
&& (!feof(scf)))
{
bBuffReloadReq = true;
}
if (bIgnoreLineOnReload)
{
if (!bBuffReloadReq)
bIgnoreLineOnReload = false;
continue;
}
line_num++;
for (i = 0; i < iMaxParallelTests; 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(g_ppcBuf[g_uiCurLineBufIdx]);
while ((i > 0 )
&& (isspace((unsigned char)g_ppcBuf[g_uiCurLineBufIdx][i - 1])))
{
i--;
if ((g_ppcBuf[g_uiCurLineBufIdx][i] == ' ')
|| (g_ppcBuf[g_uiCurLineBufIdx][i] == '\t'))
bRhsSpacePresent = true;
g_ppcBuf[g_uiCurLineBufIdx][i] = '\0';
}
if ((g_ppcBuf[g_uiCurLineBufIdx][0] == '\0')
|| (g_ppcBuf[g_uiCurLineBufIdx][0] == '#'))
{
if (bBuffReloadReq)
bIgnoreLineOnReload = true;
continue;
}
if (strncmp(g_ppcBuf[g_uiCurLineBufIdx], "test: ", 6) == 0)
test = g_ppcBuf[g_uiCurLineBufIdx] + 6;
else if (strncmp(g_ppcBuf[g_uiCurLineBufIdx], "script: ", 8) == 0)
{
bscript = true;
test = g_ppcBuf[g_uiCurLineBufIdx] + 8;
}
else if (strncmp(g_ppcBuf[g_uiCurLineBufIdx], "ignore: ", 8) == 0)
{
c = g_ppcBuf[g_uiCurLineBufIdx] + 8;
while (*c && isspace((unsigned char) *c))
c++;
add_stringlist_item(&ignorelist, c);
/*
* Note: ignore: lines do not run the test, they just say that
* failure of this test when run later on is to be ignored. A bit
* odd but that's how the shell-script version did it.
*/
continue;
}
else if (strncmp(g_ppcBuf[g_uiCurLineBufIdx], "testcon: ", 9) == 0)
{
#ifdef ENABLED_DEBUG_SYNC
test = g_ppcBuf[g_uiCurLineBufIdx] + 9;
#else
c = g_ppcBuf[g_uiCurLineBufIdx] + 9;
while (*c && isspace((unsigned char) *c))
c++;
add_stringlist_item(&ignorelist, c);
continue;
#endif
}
else
{
fprintf(stderr, _("Syntax error in ScheduleFile \"%s\" line %d: %s\n"),
schedule, line_num, g_ppcBuf[g_uiCurLineBufIdx]);
iRet = REGR_ERRCODE_SYNTAX_ERR_IN_SCH;
goto LB_ERR_HNDL_LVL_4;
}
num_tests = 0;
iRet = regrParseLineBuffer(test,
&pcLastSpace,
&num_tests,
&iMaxParallelTests,
&tests,
&resultfiles,
&expectfiles,
&tags,
&pids,
&statuses);
if (iRet != REGR_SUCCESS)
{
goto LB_ERR_HNDL_LVL_4;
}
if ((!bRhsSpacePresent)
&& (true == bBuffReloadReq)
&& (NULL != pcLastSpace)
&& (0 != strlen(pcLastSpace + 1)))
{
bHalfReadTest = true;
num_tests--;
}
/* Resetting the flag */
bRhsSpacePresent = false;
if (num_tests == 0)
{
fprintf(stderr, _("Syntax error in schedule file \"%s\" line %d: %s\n"),
schedule, line_num, g_ppcBuf[g_uiCurLineBufIdx]);
iRet = REGR_ERRCODE_SYNTAX_ERR_IN_SCH;
goto LB_ERR_HNDL_LVL_4;
}
if (bscript && (num_tests > 1))
{
fprintf(stderr,
_("Syntax error in schedule file \"%s\" line %d: %s\n"),
schedule, line_num, g_ppcBuf[g_uiCurLineBufIdx]);
iRet = REGR_ERRCODE_SYNTAX_ERR_IN_SCH;
goto LB_ERR_HNDL_LVL_4;
}
/* exception protect */
if (REG_MAX_NUM <= num_tests)
{
fprintf(stderr,
_("The max test num:%d of parallel group is large than REG_MAX_NUM:%d. \n"
"Syntax error in schedule file \"%s\" line %d: %s\n"),
num_tests, REG_MAX_NUM,
schedule, line_num, g_ppcBuf[g_uiCurLineBufIdx]);
iRet = REGR_ERRCODE_SYNTAX_ERR_IN_SCH;
goto LB_ERR_HNDL_LVL_4;
}
if (num_tests == 1)
{
if (bscript)
{
bscript = false;
status(_("script %-22s .... "), tests[0]);
REGR_START_TIMER_TEMP(0);
pids[0] = scriptExecute(tests[0], &resultfiles[0],
&expectfiles[0], &tags[0]);
}
else
{
status(_("test %-24s .... "), tests[0]);
makeNestedDirectory(tests[0]);
REGR_START_TIMER_TEMP(0);
pids[0] = (tfunc) (tests[0], &resultfiles[0],
&expectfiles[0], &tags[0]);
}
wait_for_tests(pids, statuses, NULL, 1);
REGR_STOP_TIMER_TEMP(0);
REGR_PRINT_ONEGROUP_ELAPSED_TIME;
/* status line is finished below */
}
else if (max_connections > 0 && max_connections < num_tests)
{
int oldest = 0;
i = 0;
do
{
while (i < num_tests)
{
if (i - oldest >= max_connections)
{
wait_for_tests(pids + oldest, statuses + oldest,
tests + oldest, i - oldest);
oldest = i;
}
makeNestedDirectory(tests[i]);
REGR_START_TIMER;
/* Invoke the single test file */
pids[i] = (tfunc) (tests[i], &resultfiles[i],
&expectfiles[i], &tags[i]);
i++;
}
if (false == bBuffReloadReq)
break;
/* Resetting the flag */
bBuffReloadReq = false;
iRet = regrReloadAndParseLineBuffer(&bBuffReloadReq,
&bHalfReadTest, &pcLastSpace,
scf, &num_tests, &iMaxParallelTests,
&tests, &resultfiles, &expectfiles, &tags,
&pids, &statuses);
if (iRet == REGR_EOF_REACHED)
{
break;
}
if (iRet != REGR_SUCCESS)
{
goto LB_ERR_HNDL_LVL_4;
}
} while (true);
status(_("parallel group (%d tests, in groups of %d): "),
num_tests, max_connections);
wait_for_tests(pids + oldest, statuses + oldest,
tests + oldest, i - oldest);
status_end();
g_uiCurLineBufIdx = 0;
}
else
{
i = 0;
do
{
while (i < num_tests)
{
makeNestedDirectory(tests[i]);
REGR_START_TIMER_TEMP(i);
pids[i] = (tfunc) (tests[i], &resultfiles[i],
&expectfiles[i], &tags[i]);
i++;
}
if (false == bBuffReloadReq)
break;
/* Resetting the flag */
bBuffReloadReq = false;
iRet = regrReloadAndParseLineBuffer(&bBuffReloadReq,
&bHalfReadTest, &pcLastSpace,
scf, &num_tests, &iMaxParallelTests,
&tests, &resultfiles, &expectfiles, &tags,
&pids, &statuses);
if (iRet == REGR_EOF_REACHED)
{
break;
}
if (iRet != REGR_SUCCESS)
{
goto LB_ERR_HNDL_LVL_4;
}
} while (true);
status(_("parallel group (%d tests): "), num_tests);
wait_for_tests(pids, statuses, tests, num_tests);
REGR_PRINT_ONEGROUP_ELAPSED_TIME;
status_end();
g_uiCurLineBufIdx = 0;
}
/* Check results for all tests */
for (i = 0; i < num_tests; i++)
{
_stringlist *rl,
*el,
*tl;
bool differ = false;
if (num_tests > 1)
status(_(" %-24s .... "), 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);
/* Check if diff has failed; if yes then collect the diagnosis report*/
if (newdiff && dfunc)
{
pids[0] = dfunc((char*)tests[i]);
wait_for_tests(pids, statuses, NULL, 1);
}
if (newdiff && tl)
{
printf("%s ", tl->str);
}
differ = differ || newdiff;
}
if (differ)
{
bool ignore = false;
_stringlist* sl;
for (sl = ignorelist; sl != NULL; sl = sl->next)
{
if (strcmp(tests[i], sl->str) == 0)
{
ignore = true;
break;
}
}
if (ignore)
{
status(_("%-18s"), "failed (ignored)");
fail_ignore_count++;
}
else
{
status(_("%-18s"), "FAILED");
fail_count++;
}
}
else
{
status(_("%-18s"), "ok");
success_count++;
}
REGR_PRINT_ELAPSED_TIME_TEMP(i);
if (statuses[i] != 0)
{
log_child_failure(statuses[i]);
}
/*
* Indicates the last test in the group in case of parallel test
* So end of status shall be done after the performation info
* printing
*/
}
}
LB_ERR_HNDL_LVL_4:
fclose(scf);
LB_ERR_HNDL_LVL_3:
REGR_FREE(tests);
LB_ERR_HNDL_LVL_2:
for (i = 0; i < (int)g_uiTotalBuf; i++)
{
REGR_FREE(g_ppcBuf[i]);
}
LB_ERR_HNDL_LVL_1:
REGR_FREE(g_ppcBuf);
if (iRet != REGR_SUCCESS)
exit_nicely(2);
return;
}
/*
* Run a single test
*/
static void
run_single_test(const char *test, test_function tfunc, diag_function dfunc)
{
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);
makeNestedDirectory(test);
REGR_START_TIMER;
pid = (tfunc) (test, &resultfiles, &expectfiles, &tags);
wait_for_tests(&pid, &exit_status, NULL, 1);
REGR_STOP_TIMER;
/*
* 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 */
newdiff = results_differ(test, rl->str, el->str);
/* Check if diff has failed; if yes then collect the diagnosis report*/
if (newdiff && dfunc)
{
pid = dfunc((char*)test);
wait_for_tests(&pid, &exit_status, NULL, 1);
}
if (newdiff && tl)
{
printf("%s ", tl->str);
}
differ = differ || newdiff;
}
if (differ)
{
status(_("%-18s"), "FAILED");
fail_count++;
}
else
{
status(_("%-18s"), "ok");
success_count++;
}
if (exit_status != 0)
log_child_failure(exit_status);
if (false == g_bEnablePerfDataPrint)
status_end();
REGR_PRINT_ELAPSED_TIME;
}
/*
* Create the summary-output files (making them empty if already existing)
*/
static void
open_result_files(void)
{
char file[MAXPGPATH];
FILE* difffile;
/* create the log file (copy of running status output) */
(void)snprintf(file, sizeof(file), "%s/regression.out", outputdir);
logfilename = strdup(file);
logfile = fopen(logfilename, "w");
if (!logfile)
{
fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
progname, logfilename, strerror(errno));
exit_nicely(2);
}
/* create the diffs file as empty */
(void)snprintf(file, sizeof(file), "%s/regression.diffs", outputdir);
difffilename = strdup(file);
difffile = fopen(difffilename, "w");
if (!difffile)
{
fprintf(stderr, _("%s: could not open file \"%s\" for writing: %s\n"),
progname, difffilename, strerror(errno));
exit_nicely(2);
}
/* we don't keep the diffs file open continuously */
fclose(difffile);
/* also create the output directory if not present */
(void)snprintf(file, sizeof(file), "%s/results", outputdir);
if (!directory_exists(file))
{ make_directory(file); }
}
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'" : "");
/*
* 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
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 = (char*)malloc(strlen(cwdbuf) + strlen(in) + 2);
sprintf(result, "%s/%s", cwdbuf, in);
}
canonicalize_path(result);
return result;
}
static void
help(void)
{
printf(_("PostgreSQL regression test driver\n"));
printf(_("\n"));
printf(_("Usage: %s [options...] [extra tests...]\n"), progname);
printf(_("\n"));
printf(_("Options:\n"));
printf(_(" --dbname=DB use database DB (default \"regression\")\n"));
printf(_(" --debug turn on debug mode in programs that are run\n"));
printf(_(" --inputdir=DIR take input files from DIR (default \".\")\n"));
printf(_(" --load-language=lang load the named language before running the\n"));
printf(_(" tests; can appear multiple times\n"));
printf(_(" --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(_(" --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(_(" --dlpath=DIR look for dynamic libraries in DIR\n"));
printf(_(" --temp-install=DIR create a temporary installation in DIR\n"));
printf(_(" --use-existing use an existing installation\n"));
printf(_(" --launcher=CMD use CMD as launcher of gsql\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(_(" --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(_(" --hdfshostname=IPAdress hdfs data IP adress\n"));
printf(_(" --hdfsstoreplus=hdfsstoreplus hdfs data store path plus information\n"));
printf(_(" --hdfscfgpath=ConfigPath config path for principle and keytab file path\n"));
printf(_(" --hdfsport=N hdfs data port\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 gsql in DIR (default: find in PATH)\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"));
}
static
int
initialize_myinfo(
int coordnode_num,
int datanode_num,
int init_port,
bool keep_last_time_data,
bool run_test_case)
{
int i;
int* coordnode_port;
int* coordnode_pooler_port;
int* datanode_port;
int* datanode_pooler_port;
int* datanode_stream_ctl_port;
int* datanode_sctp_port;
/* if standby configed. */
int* dns_port = NULL;
int* dns_sctp_port = NULL;
int* dns_ctl_port = NULL;
int* dn_primary_port = NULL;
int* dn_standby_port = NULL;
int* dn_secondary_port = NULL;
coordnode_port = (int*)malloc(sizeof(int) * coordnode_num);
coordnode_pooler_port = (int*)malloc(sizeof(int) * coordnode_num);
datanode_pooler_port = (int*)malloc(sizeof(int) * datanode_num);
datanode_port = (int*)malloc(sizeof(int) * datanode_num);
datanode_stream_ctl_port = (int*)malloc(sizeof(int) * datanode_num);
datanode_sctp_port = (int*)malloc(sizeof(int) * datanode_num);
if (standby_defined)
{
dns_port = (int*)malloc(sizeof(int) * datanode_num);
dns_ctl_port = (int*)malloc(sizeof(int) * datanode_num);
dns_sctp_port = (int*)malloc(sizeof(int) * datanode_num);
dn_primary_port = (int*)malloc(sizeof(int) * datanode_num);
dn_standby_port = (int*)malloc(sizeof(int) * datanode_num);
dn_secondary_port = (int*)malloc(sizeof(int) * datanode_num);
}
for (i = 0 ; i < coordnode_num; i++)
{
coordnode_port[i] = init_port++;
}
for (i = 0 ; i < coordnode_num; i++)
{
coordnode_pooler_port[i] = init_port++;
}
/* reserve port for datanode instance */
for (i = 0 ; i < datanode_num; i++)
{
datanode_port[i] = init_port++;
datanode_sctp_port[i] = init_port++; // Reserve sctp port for datanode.
datanode_pooler_port[i] = init_port++;
datanode_stream_ctl_port[i] = init_port++;
if (standby_defined)
{
dns_port[i] = init_port++;
dns_sctp_port[i] = init_port++;
dns_ctl_port[i] = init_port++;
dn_primary_port[i] = init_port++;
dn_standby_port[i] = init_port++;
dn_secondary_port[i] = init_port++;
}
}
myinfo.co_num = coordnode_num;
myinfo.dn_num = datanode_num;
myinfo.co_port = coordnode_port;
myinfo.co_pool_port = coordnode_pooler_port;
myinfo.dn_port = datanode_port;
myinfo.dns_port = dns_port;
myinfo.dn_pool_port = datanode_pooler_port;
myinfo.dn_ctl_port = datanode_stream_ctl_port;
myinfo.dn_sctp_port = datanode_sctp_port;
myinfo.dns_ctl_port = dns_ctl_port;
myinfo.dns_sctp_port = dns_sctp_port;
myinfo.dn_primary_port = dn_primary_port;
myinfo.dn_standby_port = dn_standby_port;
myinfo.dn_secondary_port = dn_secondary_port;
myinfo.gtm_port = init_port;
myinfo.keep_data = keep_last_time_data;
myinfo.run_check = run_test_case;
myinfo.shell_count = 0;
return 0;
}
/*
* Compares two strings case insensitively. Returns 0 if the strings matched.
* A non zero value otherwise
*/
static int
regressStrncasecmp(const char *rs1, const char *rs2, int iMaxLenToCmp)
{
int i;
int *piMaxLenCmp = &iMaxLenToCmp;
if (iMaxLenToCmp == -1)
piMaxLenCmp = &i;
for (i = 1; i <= *piMaxLenCmp; i++)
{
unsigned char ch1 = (unsigned char) *rs1++;
unsigned char ch2 = (unsigned char) *rs2++;
if (ch1 != ch2)
{
/* Convert all into lower case */
if ((ch1 >= 'A') && (ch1 <= 'Z'))
ch1 += 'a' - 'A';
if ((ch2 >= 'A') && (ch2 <= 'Z'))
ch2 += 'a' - 'A';
/* String did not match */
if (ch1 != ch2)
return (int)ch1 - (int)ch2;
}
if (ch1 == 0)
break;
}
return 0;
}
/*
* Validate the configuration item value of type: "on" or "off"
*/
static bool
regrValidateBoolConfigVal(const char* pcConfigValue, int iConfId)
{
int len = strlen(pcConfigValue);
if (len == 0)
{
fprintf(stderr, _("\n Value not specified for configuration item: [%s]."
"Value should be \'off\' or \'on\'!\n"),
aacRegConfItemName[iConfId]);
return false;
}
if ((0 != regressStrncasecmp(pcConfigValue, "on", -1))
&& (0 != regressStrncasecmp(pcConfigValue, "off", -1)))
{
fprintf(stderr, _("\n Value for the configuration item: [%s] should be"
" \'on\' or \'off\'. User has given: \'%s\'\n"),
aacRegConfItemName[iConfId], pcConfigValue);
return false;
}
return true;
}
/* Set the value of the configuration item: "column_name_present" into the
* globals */
static void
processTuplesOnlyConfItem(char* pcConfigValue)
{
if (false == regrValidateBoolConfigVal(pcConfigValue, REGR_TUPONLY_ID))
exit(2);
/* column_name_present = off ie tuples only */
if (0 == regressStrncasecmp(pcConfigValue, "off", -1))
(void)strncpy((char *)g_stRegrConfItems.acTuplesOnly,
"-t",
(MAX_TUPLE_ONLY_STRLEN - 1));
}
/* Set the value of the configuration item:"column_separator" into the globals*/
static void
processColSepConfItem(const char* pcConfigValue)
{
int len;
char *pcTmp = (char *)pcConfigValue;
len = strlen(pcTmp);
if (len == 0)
{
fprintf(stderr, _("\n Value not specified for configuration item: [%s]!"
"Value should be a string, indicating the field separator. \n"),
aacRegConfItemName[REGR_COL_SEP_ID]);
exit(2);
}
if (*pcTmp != '\'')
{
fprintf(stderr, _("\n Syntax error in the regress "
"configuration item:%s! \' missing at the start of "
"the value! \n"), aacRegConfItemName[REGR_COL_SEP_ID]);
exit(2);
}
/* Ignoring the start ' character */
pcTmp++;
len--;
if ((len == 0)
|| (pcTmp[len - 1] != '\''))
{
fprintf(stderr, _("\n Syntax error in the regress "
"configuration item:%s! \' missing at the end of "
"the value! \n"), aacRegConfItemName[REGR_COL_SEP_ID]);
exit(2);
}
/* Overwriting the end ' character */
pcTmp[len - 1] = '\0';
len = strlen(pcTmp);
/* User has given empty string. If no column separator is required,
* Space character shall be given but cannot be empty */
if (len == 0)
{
fprintf(stderr, _("\n User has given empty string. If no column "
"separator is required, Space character shall be given but it "
"cannot be empty. \n"));
exit(2);
}
if (len > MAX_COLUMN_SEP_STRLEN)
{
fprintf(stderr, _("\n Maximum allowed length of the column separator "
"string is %d!! Given length: %d. Given string: [%s].\n"),
MAX_COLUMN_SEP_STRLEN , len, pcTmp);
exit(2);
}
sprintf((char *)g_stRegrConfItems.acFieldSepForAllText, "-C\"%s\"", pcTmp);
}
/* Process the configuration item: performance_data_printing */
static void
processPerfDataLoggingConf(const char* pcConfigValue)
{
if (false == regrValidateBoolConfigVal(pcConfigValue,
REGR_PERF_DATA_LOGGING_ID))
exit(2);
if (0 == regressStrncasecmp(pcConfigValue, "on", -1))
g_bEnablePerfDataPrint = true;
}
static void
processDiagCollectorConf(const char* pcConfigValue)
{
if (false == regrValidateBoolConfigVal(pcConfigValue,
REGR_DIAG_COLLECT_ID))
exit(2);
if (0 == regressStrncasecmp(pcConfigValue, "on", -1))
g_bEnableDiagCollection = true;
}
/* Initialize of the module-global structure instance for storing the
* replacement pattern strings */
static void
regrInitReplcPattStruct(void)
{
g_stRegrReplcPatt.iNumOfPatterns = 0;
g_stRegrReplcPatt.iRemainingPattBuffSize = 0;
g_stRegrReplcPatt.iMaxNumOfPattern = REGR_NUM_OF_REPLC_ITEMS_CHUNK;
g_stRegrReplcPatt.puiPatternOffset = NULL;
g_stRegrReplcPatt.puiPattReplValOffset = NULL;
g_stRegrReplcPatt.pcBuf = NULL;
}
/*
* This functions checks whether the line starts with a global variable name i.e. @....@.
* If yes, it returns true.
* ppcRegConfLine will be pointing to the character after the global
* variable name (maybe to the '=' character)
* piPattLen will be having the length of the global variable name.
*
* If no, it returns false.
*/
static bool
regrIsStringPatternToBeReplaced(char** ppcRegConfLine, int *piPattLen)
{
char* pcCurLine = *ppcRegConfLine;
/* Format for specifying the replacement string is
* @VAR@ = 'VALUE'
* So the first character should be REGR_REPLC_PATT_START_CHAR */
if (*(pcCurLine++) != REGR_REPLC_PATT_START_CHAR)
return false;
/* Setting the length of the replacement pattern string as 1 on counting @*/
*piPattLen = 1;
while ((*pcCurLine != '\0')
&& (*pcCurLine != ' ')
&& (*pcCurLine != '\t')
&& (*pcCurLine != '='))
{
(*piPattLen)++;
pcCurLine++;
}
if ((*piPattLen == 1) || ((*(pcCurLine - 1)) != REGR_REPLC_PATT_END_CHAR))
{
fprintf(stderr, _("\n Missing %c at the end of the pattern replacement "
"string! FORMAT is @PATTERN_NAME@ = \'VALUE\' . Line: [%s] \n"),
REGR_REPLC_PATT_END_CHAR, *ppcRegConfLine);
return false;
}
/* LHS cant be @@ */
if (*piPattLen == 2)
{
fprintf(stderr, _("\n Replacement Pattern string should not be %c%c\n"),
REGR_REPLC_PATT_START_CHAR, REGR_REPLC_PATT_END_CHAR);
return false;
}
/* Replacement pattern string should be immediately followed by space or =*/
if ((*pcCurLine != ' ') && (*pcCurLine != '\t') && (*pcCurLine != '='))
{
fprintf(stderr, _("\n Replacement pattern string should be immediately "
"followed by \'space\' or \'Equal to\' character."
" Given line: [%s]\n"), *ppcRegConfLine);
return false;
}
while ((*pcCurLine == ' ') || (*pcCurLine == '\t'))
pcCurLine++;
*ppcRegConfLine = pcCurLine;
return true;
}
/* Checks whether the current line from regress.conf is an entry for the
* replacement pattern string.
*
* If yes,
* piPattLen will be pointing to the length of the replacement pattern string
* ppcReplValue will be pointing to the string to be replaced with
* piValLen will be pointing to the length of the string to be replaced with
* Function returns true
*
* If no,
* Function returns false.
*/
static bool
regrIsItemReplcPatt(const char *pcLine, int *piPattLen,
char **ppcReplValue, int *piValLen)
{
int len;
char *pcCurLine = (char*)pcLine;
/* Checking whether the start of the line contains a GLOBAL VARIABLE, i.e.
* @....@ */
if(false == regrIsStringPatternToBeReplaced(&pcCurLine, piPattLen))
return false;
/* Skipping the pattern name - value separator character: '=' */
if (*pcCurLine == '=')
pcCurLine++;
regrTrimString(&pcCurLine);
if (*pcCurLine == '\0')
{
fprintf(stderr, _("\n Missing value for the replacement pattern"
"string! FORMAT is @PATTERN_NAME@ = \'VALUE\' . Line: [%s] \n"),
pcLine);
return false;
}
len = strlen(pcCurLine);
/* Validation to ensure that the value is given within ' or " characters */
if ((len == 1)
|| ((*pcCurLine != '\'') && (*pcCurLine != '\"'))
|| ((pcCurLine[len - 1] != '\'') && (pcCurLine[len - 1] != '\"')))
{
fprintf(stderr, _("\n Value should be enclosed within \' or \" "
"characters. Given Value: [%s]\n"), pcCurLine);
return false;
}
/* Removing the ' or " at the end of the value */
pcCurLine[len - 1] = '\0';
/* Avoiding the ' or " at the beginning of the value and updating the
* output parameter */
*ppcReplValue = pcCurLine + 1;
*piValLen = strlen(*ppcReplValue);
return true;
}
/* Allocates memory for the storage of the replacement pattern strings, their
* corresponding replacement strings, and the index for the both. The offset to
* the location which is going to be used for the storage of the replacement
* pattern string, will be initialized to the index slot to be used next.
*
* Memory Layout:
*
* INDEX 1 INDEX 2 STORAGE SPACE
* +----------------+-------------------+-----------------------------+
* | Offsets to the | Offsets to | Actual storage for Patterns |
* | Patterns to be | the strings to be | to be replaced and their |
* | replaced | replaced for | corresponding values |
* | | the given patterns| They gets stored as pairs. |
* | | | \0 is explicitly stored. |
* +----------------+-------------------+-----------------------------+
* ^
* |
* Offset start
* i.e. g_stRegrReplcPatt.pcBuf
*/
static int
regrAllocMemForReplcPatt()
{
int iBuffSize;
int iOldMaxNumOfPatt = 0;
int iSizeOfIdxSect;
unsigned int *puiPattBkup = NULL;
/* Case of reallocation. Back up the address of the old memory block */
if (g_stRegrReplcPatt.puiPatternOffset != NULL)
{
puiPattBkup = g_stRegrReplcPatt.puiPatternOffset;
iOldMaxNumOfPatt = g_stRegrReplcPatt.iMaxNumOfPattern;
g_stRegrReplcPatt.iMaxNumOfPattern += REGR_NUM_OF_REPLC_ITEMS_CHUNK;
}
/* Size of the INDEX BLOCK for the easy access of the storage buffer */
iSizeOfIdxSect
= REGR_SIZE_OF_INDEX_ELEM * g_stRegrReplcPatt.iMaxNumOfPattern;
/* Size of the buffer excluding the offset sections */
iBuffSize = (REGR_AVG_REPLC_PATT_LEN + REGR_AVG_REPLC_PATT_VAL_LEN)
* g_stRegrReplcPatt.iMaxNumOfPattern;
/* Allocating memory for storing the pattern replacement strings
* from the regress.conf file */
g_stRegrReplcPatt.puiPatternOffset
= (unsigned int *)malloc((iSizeOfIdxSect * 2) + iBuffSize);
if (g_stRegrReplcPatt.puiPatternOffset == NULL)
{
fprintf(stderr, _("Could not allocate memory for parsing "
"regress.conf file! Requested Size: %d. \n"),
(int)((iSizeOfIdxSect * 2) + iBuffSize));
return REGR_ERRCODE_MALLOC_FAILURE;
}
memset(g_stRegrReplcPatt.puiPatternOffset, '\0',
(iSizeOfIdxSect * 2) + iBuffSize);
/* Initialize the remaining size of the storage buffer as the storage buffer
* size of the newly allocated memory */
g_stRegrReplcPatt.iRemainingPattBuffSize = iBuffSize;
g_stRegrReplcPatt.puiPattReplValOffset
= (unsigned int *)(((char*)(void*)g_stRegrReplcPatt.puiPatternOffset)
+ iSizeOfIdxSect);
g_stRegrReplcPatt.pcBuf
= ((char*)(void*)g_stRegrReplcPatt.puiPattReplValOffset) + iSizeOfIdxSect;
/* Copy contents of old memory block into newly allocated memory */
if (puiPattBkup != NULL)
{
/* Copying the offsets of replacement pattern strings */
memcpy(g_stRegrReplcPatt.puiPatternOffset,
puiPattBkup,
REGR_SIZE_OF_INDEX_ELEM * iOldMaxNumOfPatt);
/* Copying offsets of 'values' of the 'replacement pattern strings' */
memcpy(g_stRegrReplcPatt.puiPattReplValOffset,
(char*)(void*)puiPattBkup
+ (REGR_SIZE_OF_INDEX_ELEM * iOldMaxNumOfPatt),
REGR_SIZE_OF_INDEX_ELEM * iOldMaxNumOfPatt);
/* Copying the core contents of the old memory block excluding the index
* area */
memcpy(g_stRegrReplcPatt.pcBuf,
(char*)(void*)puiPattBkup
+ (REGR_SIZE_OF_INDEX_ELEM * iOldMaxNumOfPatt * 2),
((REGR_AVG_REPLC_PATT_LEN + REGR_AVG_REPLC_PATT_VAL_LEN)
* iOldMaxNumOfPatt));
if (g_stRegrReplcPatt.iNumOfPatterns > 0)
{
int iOffIdx = g_stRegrReplcPatt.iNumOfPatterns;
int iPrevOffset = g_stRegrReplcPatt.puiPatternOffset[iOffIdx - 1];
int iNewOffset = iPrevOffset
+ strlen(&(g_stRegrReplcPatt.pcBuf[iPrevOffset]))
+ 1;
/* Updating the offset of the next string's location, which is going
* to be used */
g_stRegrReplcPatt.puiPatternOffset[iOffIdx] = iNewOffset;
/* Subtracting the size of the copied contents from the
* 'new total buffer size' */
g_stRegrReplcPatt.iRemainingPattBuffSize
-= (iBuffSize - iNewOffset);
}
/* Freeing the old memory block */
REGR_FREE(puiPattBkup);
}
return REGR_SUCCESS;
}
/* Load the replacement pattern strings and their values into global memory */
static void
regrProcessReplcPattLine(const char* pcLine,
int iPattLen,
const char* pcReplcPattValue,
int iPattValueLen)
{
int iIdx = g_stRegrReplcPatt.iNumOfPatterns;
unsigned int *puiPattOff = g_stRegrReplcPatt.puiPatternOffset;
unsigned int *puiValOff = g_stRegrReplcPatt.puiPattReplValOffset;
char *pcBuf = g_stRegrReplcPatt.pcBuf;
unsigned int uiCurrOff = puiPattOff[iIdx];
/* Load the pattern string to be replaced into global memory */
(void)strncpy((char*)&(pcBuf[puiPattOff[iIdx]]), pcLine, iPattLen);
/* Update the offset of the replacement pattern string's value in the
* corresponding index section. + 1 is considering the \0. Also note that
* we are not keeping \0 explicitly because the entire buffer is memset with
* 0 */
puiValOff[iIdx] = uiCurrOff + iPattLen + 1;
/* Load the value to be replaced for the pattern string, into global
* memory */
(void)strncpy((char*)&(pcBuf[puiValOff[iIdx]]),
pcReplcPattValue,
iPattValueLen);
/* One pattern and its values is now loaded. So increment the loaded
* pattern count */
g_stRegrReplcPatt.iNumOfPatterns++;
/* If this IF CHECK is false, then we have filled the last pattern and its
* value. If next pair has to be loaded then memory need to be extented.
* Next offset will be updated during that time and here nothing is to be
* done */
if (g_stRegrReplcPatt.iNumOfPatterns < g_stRegrReplcPatt.iMaxNumOfPattern)
/* Updating the offset to the next usable location */
puiPattOff[g_stRegrReplcPatt.iNumOfPatterns]
= puiValOff[iIdx] + (unsigned int)iPattValueLen + (unsigned int)1;
/* +2 is considering the \0 */
g_stRegrReplcPatt.iRemainingPattBuffSize -= (iPattLen + iPattValueLen + 2);
}
/* Load the regress configuration item values into the globals */
static void
loadRegressConf(char* pcRegConfigFile)
{
int len;
int iRegConfInd;
int iPattLen = 0;
int iPattValueLen;
bool bLineValid;
char buf[MAX_LINE_LEN];
char *pcLine;
char *pcTemp;
char *pcReplcPattValue;
FILE *pfRegConf;
/* Open the regress.conf file */
pfRegConf = fopen(pcRegConfigFile, "r");
if (NULL == pfRegConf)
{
fprintf(stderr, _("\n Could not open the regression configuration "
"file: [%s]. Continuing with default values.\n"),
pcRegConfigFile);
return;
}
/* Process each line in the regress.conf file */
while (fgets(buf, sizeof(buf), pfRegConf) != NULL)
{
pcLine = buf;
/* Remove white space on LHS and white space/CarriageReturn on RHS */
regrTrimString(&pcLine);
/* # at the start of the line indicates a full comment line. Entire line
* can be ignored */
if (*pcLine == '#')
continue;
/* Empty lines can be ignored */
if (*pcLine == '\0')
continue;
/* Comments can be started any where in the line. RHS of #, in a line,
* can be ignored */
pcTemp = strstr(pcLine, "#");
if (pcTemp != NULL)
*pcTemp = '\0';
bLineValid = false;
for (iRegConfInd = 0; iRegConfInd < MAX_REG_CONF_ITEMS; iRegConfInd++)
{
len = strlen(aacRegConfItemName[iRegConfInd]);
if (0 == regressStrncasecmp(pcLine,
aacRegConfItemName[iRegConfInd], len))
{
pcTemp = pcLine + len;
/* Configuration Item name should be immediately followed by */
if ((*pcTemp != ' ') && (*pcTemp != '\t') && (*pcTemp != '='))
continue;
while ((*pcTemp == ' ') || (*pcTemp == '\t'))
pcTemp++;
if (*pcTemp == '=')
pcTemp++;
regrTrimString(&pcTemp);
/* pcTemp will be now containing the value */
switch (iRegConfInd)
{
case REGR_TUPONLY_ID:
processTuplesOnlyConfItem(pcTemp);
break;
case REGR_COL_SEP_ID:
processColSepConfItem(pcTemp);
break;
case REGR_PERF_DATA_LOGGING_ID:
processPerfDataLoggingConf(pcTemp);
break;
case REGR_DIAG_COLLECT_ID:
processDiagCollectorConf(pcTemp);
break;
}
/* Marking the line regress.conf as valid */
bLineValid = true;
/* Line matches with one of the config item. So process and get
* next line */
break;
}
}
if (bLineValid == false)
{
regrTrimString(&pcLine);
if (regrIsItemReplcPatt(pcLine, &iPattLen, &pcReplcPattValue,
&iPattValueLen))
{
if ((g_stRegrReplcPatt.iNumOfPatterns == 0)
|| (g_stRegrReplcPatt.iNumOfPatterns
== g_stRegrReplcPatt.iMaxNumOfPattern)
|| ((iPattLen + iPattValueLen + 2)
> g_stRegrReplcPatt.iRemainingPattBuffSize))
{
do
{
if (regrAllocMemForReplcPatt() != REGR_SUCCESS)
goto LB_MALLOC_REPLC_PATT_ERR;
} while ((iPattLen + iPattValueLen + 2)
> g_stRegrReplcPatt.iRemainingPattBuffSize);
}
regrProcessReplcPattLine(pcLine,
iPattLen,
pcReplcPattValue,
iPattValueLen);
}
else
fprintf(stderr, _("\n Unknown Configuration item. Line: [%s] "
"ignored. \n"), pcLine);
}
} /* while (fgets ... */
LB_MALLOC_REPLC_PATT_ERR:
fclose(pfRegConf);
}
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
drop_database_if_exists(const char *dbname)
{
header(_("dropping database \"%s\""), dbname);
psql_command("postgres", "DROP DATABASE IF EXISTS \"%s\"", dbname);
}
int
regression_main(int argc, char* argv[], init_function ifunc, test_function tfunc, diag_function dfunc)
{
_stringlist* ssl;
int c;
int i;
int option_index;
char buf[MAXPGPATH * 4];
char buf2[MAXPGPATH * 4] = {0};
int coordnode_num = 2;
int datanode_num = 2;
int init_port = 25632;
bool keep_last_time_data = false;
bool run_test_case = true;
bool override_installdir = false;
struct timeval start_time;
struct timeval end_time;
double total_time;
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-install", 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},
{"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},
{"noclean", no_argument, NULL, 24},
{"regconf", required_argument, NULL, 25},
{"hdfshostname", required_argument, NULL, 26},
{"hdfsport", required_argument, NULL, 27},
{"hdfscfgpath", required_argument, NULL, 28},
{"hdfsstoreplus", required_argument, NULL, 29},
{"keep_last_data", required_argument, NULL, 30},
{NULL, 0, NULL, 0}
};
progname = get_progname(argv[0]);
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, "b:c:d:hp:r:s:V:n:w", long_options, &option_index)) != -1)
{
switch (c)
{
case 'h':
help();
exit_nicely(0);
/* fall through */
case 'V':
puts("pg_regress (PostgreSQL) " PG_VERSION);
exit_nicely(0);
/* fall through */
case 'd':
datanode_num = atoi(optarg);
break;
case 'c':
coordnode_num = atoi(optarg);
break;
case 'p':
init_port = atoi(optarg);
break;
case 'r':
run_test_case = atoi(optarg);
break;
case 'b':
temp_install = make_absolute_path(optarg);
override_installdir = true;
break;
case 'm':
if( strncmp(optarg, "tcp", 3) != 0)
comm_tcp_mode = false;
break;
case 's':
standby_defined = atoi(optarg);
break;
case 'w':
change_password = true;
break;
case 'n':
dop = atoi(optarg);
break;
case 1:
/*
* 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;
break;
case 3:
inputdir = strdup(optarg);
break;
case 4:
add_stringlist_item(&loadlanguage, optarg);
break;
case 5:
max_connections = atoi(optarg);
break;
case 6:
encoding = strdup(optarg);
break;
case 7:
outputdir = strdup(optarg);
break;
case 8:
add_stringlist_item(&schedulelist, optarg);
break;
case 9:
if(override_installdir == false)
temp_install = make_absolute_path(optarg);
break;
case 10:
nolocale = true;
break;
case 11:
top_builddir = strdup(optarg);
break;
case 13:
hostname = strdup(optarg);
break;
case 14:
port = atoi(optarg);
port_specified_by_user = true;
break;
case 15:
user = strdup(optarg);
break;
case 16:
/* "--psqldir=" should mean to use PATH */
if (strlen(optarg))
{ psqldir = strdup(optarg); }
break;
case 17:
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;
case 24:
clean = false;
break;
case 25:
pcRegConfFile = strdup(optarg);
break;
case 26:
hdfshostname = strdup(optarg);
break;
case 27:
hdfsport = strdup(optarg);
break;
case 28:
hdfscfgpath = strdup(optarg);
break;
case 29:
hdfsstoreplus = strdup(optarg);
break;
case 30:
{
char *tmp_str = strdup(optarg);
if (pg_strncasecmp(tmp_str, "true", 4) == 0)
{
keep_last_time_data = true;
}
else
{
keep_last_time_data = false;
}
break;
}
default:
/* getopt_long already emitted a complaint */
fprintf(stderr, _("\nTry \"%s -h\" for more information.\n"),
progname);
exit_nicely(2);
}
}
memset(g_stRegrConfItems.acFieldSepForAllText,
'\0', sizeof(g_stRegrConfItems.acFieldSepForAllText));
memset(g_stRegrConfItems.acTuplesOnly,
'\0', sizeof(g_stRegrConfItems.acTuplesOnly));
regrInitReplcPattStruct();
if (pcRegConfFile)
loadRegressConf(pcRegConfFile);
int result = initialize_myinfo(coordnode_num,
datanode_num,
init_port,
keep_last_time_data,
run_test_case);
if (result != 0)
{ return -1; }
/*
* if we still have arguments, they are extra tests to run
*/
while (argc - optind >= 1)
{
add_stringlist_item(&extra_tests, argv[optind]);
optind++;
}
if (temp_install && !port_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);
loginuser = get_id();
// Do not stop postmaster if we are told not to run test cases.
//
if (run_test_case)
(void)atexit(stop_postmaster);
/*
* Initialization
*/
open_result_files();
initialize_environment();
#if defined(HAVE_GETRLIMIT) && defined(RLIMIT_CORE)
unlimit_core_size();
#endif
//temp_install = NULL;
if (temp_install)
{
/*
* Prepare the temp installation
*/
if (!top_builddir)
{
fprintf(stderr, _("--top-builddir must be specified when using --temp-install\n"));
exit_nicely(2);
}
if (!directory_exists(temp_install))
{
myinfo.keep_data = false;
}
#ifndef ENABLE_LLT
if (myinfo.keep_data)
{
// header(_("removing existing gtm file"));
// rmtree(temp_gtm_install, true);
/* "make install" */
#ifndef WIN32_ONLY_COMPILER
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s\" -C \"%s\" DESTDIR=\"%s/install\" install -sj > \"%s/log/install.log\" 2>&1" SYSTEMQUOTE,
makeprog, top_builddir, temp_install, outputdir);
#else
(void)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))
{
fprintf(stderr, _("\n%s: installation failed\nExamine %s/log/install.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
exit_nicely(2);
}
}
#endif
if (myinfo.keep_data == false)
{
#ifndef ENABLE_LLT
if (directory_exists(temp_install))
{
header(_("removing existing temp installation"));
(void)rmtree(temp_install, true);
}
header(_("creating temporary installation"));
/* make the temp install top directory */
make_directory(temp_install);
#endif
/* and a directory for log files */
(void)snprintf(buf, sizeof(buf), "%s/log", outputdir);
if (!directory_exists(buf))
{ make_directory(buf); }
#ifdef ENABLE_LLT
if (directory_exists(temp_install))
{
snprintf(buf, sizeof(buf),
SYSTEMQUOTE "rm -rf %s/coordinator* > %s/log/install.log 2>&1" SYSTEMQUOTE,
temp_install, outputdir);
header("%s", buf);
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);
}
snprintf(buf, sizeof(buf),
SYSTEMQUOTE "rm -rf %s/data* > %s/log/install.log 2>&1" SYSTEMQUOTE,
temp_install, outputdir);
header("%s", buf);
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);
}
}
#endif
#ifndef ENABLE_LLT
/* "make install" */
#ifndef WIN32_ONLY_COMPILER
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s\" -C \"%s\" DESTDIR=\"%s/install\" install -sj > \"%s/log/install.log\" 2>&1" SYSTEMQUOTE,
makeprog, top_builddir, temp_install, outputdir);
#else
(void)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))
{
fprintf(stderr, _("\n%s: installation failed\nExamine %s/log/install.log for the reason.\nCommand was: %s\n"), progname, outputdir, buf);
exit_nicely(2);
}
for (ssl = extra_install; ssl != NULL; ssl = ssl->next)
{
#ifndef WIN32_ONLY_COMPILER
(void)snprintf(buf, sizeof(buf),
SYSTEMQUOTE "\"%s\" -C \"%s/%s\" DESTDIR=\"%s/install\" install >> \"%s/log/install.log\" 2>&1" SYSTEMQUOTE,
makeprog, top_builddir, ssl->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);
}
}
#endif
/* initdb */
header(_("initializing database system"));
init_gtm();
/* Initialize nodes */
initdb_node_info(standby_defined);
/*
* 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.)
* Update configuration file of each node with user-defined options
* and 2PC related information.
* PGXCTODO: calculate port of GTM before setting configuration files
*/
initdb_node_config_file(standby_defined);
}
/*Execute shell cmds in source files*/
exec_cmds_from_inputfiles();
/*
* Start the temp postmaster
*/
header(_("starting postmaster"));
/* Start GTM */
start_gtm();
start_my_node(0, COORD, true, false);
for (i = 1 ; i < myinfo.co_num ; i++)
{
start_my_node(i, COORD, false, false);
}
for (i = 0 ; i < myinfo.dn_num; i++)
{
start_my_node(i, DATANODE, false, standby_defined);
}
pg_usleep(5000000L);
/*
* 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.
*/
for (i = 0; i < 60; i++)
{
/* Done if psql succeeds */
if (system(buf2) == 0)
{
break;
}
/* Check node failure */
for (i = 0 ; i < myinfo.co_num ; i++)
{
check_node_fail(i, COORD);
}
for (i = 0 ; i < myinfo.dn_num ; i++)
{
check_node_fail(i, DATANODE);
}
}
if (i >= 60)
{
/* If one node fails, all fail */
for (i = 0 ; i < myinfo.co_num ; i++)
{
kill_node(i, COORD);
}
for (i = 0 ; i < myinfo.dn_num ; i++)
{
kill_node(i, DATANODE);
}
for (i = 0; i < myinfo.shell_count; i++)
{
kill_node(i, SHELL);
}
exit_nicely(2);
}
if (standby_defined)
{
for (i = 0; i < myinfo.dn_num; i++)
{
setup_datanode_replication(i);
}
}
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
/* Print info for each node */
for (i = 0 ; i < myinfo.co_num ; i++)
printf(_("running on port %d with PID %lu for Coordinator %d\n"),
get_port_number(i, COORD), ULONGPID(get_node_pid(i, COORD)), i+1);
for (i = 0 ; i < myinfo.dn_num; i++)
printf(_("running on port %d with PID %lu for Datanode %d\n"),
get_port_number(i, DATANODE), ULONGPID(get_node_pid(i, DATANODE)), i+1);
/* Postmaster is finally running, so set up connection information on Coordinators */
if (myinfo.keep_data == false)
{
setup_connection_information(standby_defined);
}
else
{
pg_usleep(2000000L);
rebuild_node_group();
for (ssl = dblist; ssl; ssl = ssl->next)
drop_database_if_exists(ssl->str);
for (ssl = extraroles; ssl; ssl = ssl->next)
drop_role_if_exists(ssl->str);
_stringlist* tmpdblist = NULL;
_stringlist* tmprolelist = NULL;
add_stringlist_item(&tmpdblist, "cstore_vacuum_full_db");
add_stringlist_item(&tmpdblist, "test_sort");
add_stringlist_item(&tmpdblist, "td_db");
add_stringlist_item(&tmpdblist, "my_db1");
add_stringlist_item(&tmpdblist, "td_db_char");
add_stringlist_item(&tmpdblist, "td_db_char_cast");
add_stringlist_item(&tmpdblist, "tdtest");
add_stringlist_item(&tmpdblist, "td_db_char_bulkload");
add_stringlist_item(&tmpdblist, "testaes1");
add_stringlist_item(&tmpdblist, "testaes2");
for (ssl = tmpdblist; ssl; ssl = ssl->next)
drop_database_if_exists(ssl->str);
add_stringlist_item(&tmprolelist, "temp_reset_user");
add_stringlist_item(&tmprolelist, "cstore_role");
add_stringlist_item(&tmprolelist, "test_llt");
add_stringlist_item(&tmprolelist, "alter_llt2");
add_stringlist_item(&tmprolelist, "normal_usr");
add_stringlist_item(&tmprolelist, "llt_1");
add_stringlist_item(&tmprolelist, "llt_3");
add_stringlist_item(&tmprolelist, "llt_5");
add_stringlist_item(&tmprolelist, "role_pwd_complex");
add_stringlist_item(&tmprolelist, "dfm");
add_stringlist_item(&tmprolelist, "ad1");
add_stringlist_item(&tmprolelist, "ad2");
add_stringlist_item(&tmprolelist, "hs");
for (ssl = tmprolelist; ssl; ssl = ssl->next)
drop_role_if_exists(ssl->str);
}
if(run_test_case == false)
gen_startstop_script();
}
else
{
/*
* Using an existing installation, so may need to get rid of
* pre-existing database(s) and role(s)
*/
if (myinfo.run_check == true && !use_existing)
{
for (ssl = dblist; ssl; ssl = ssl->next)
drop_database_if_exists(ssl->str);
for (ssl = extraroles; ssl; ssl = ssl->next)
drop_role_if_exists(ssl->str);
}
}
if (myinfo.run_check == true)
{
if(!use_existing)
{
for (ssl = dblist; ssl; ssl = ssl->next)
{ create_database(ssl->str); }
for (ssl = extraroles; ssl; ssl = ssl->next)
{ create_role(ssl->str, dblist); }
}
/*
* Ready to run the tests
*/
header(_("running regression test queries"));
char *old_gausshome = NULL;
char env_path[MAXPGPATH + sizeof("GAUSSHOME=")];
// Query retry need env 'GAUSSHOME'.
//
old_gausshome = gs_getenv_r("GAUSSHOME");
(void)snprintf(env_path, sizeof(env_path), "GAUSSHOME=%s/../", psqldir);
gs_putenv_r(env_path);
(void)gettimeofday(&start_time, NULL);
if (!g_bEnableDiagCollection)
dfunc = NULL;
for (ssl = schedulelist; ssl != NULL; ssl = ssl->next)
{
run_schedule(ssl->str, tfunc, dfunc);
}
for (ssl = extra_tests; ssl != NULL; ssl = ssl->next)
{
run_single_test(ssl->str, tfunc, dfunc);
}
(void)gettimeofday(&end_time, NULL);
// Restore the env 'GAUSSHOME'
//
if (old_gausshome)
{
(void)snprintf(env_path, sizeof(env_path), "GAUSSHOME=%s", old_gausshome);
gs_putenv_r(env_path);
}
total_time = (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_usec - start_time.tv_usec)* 0.000001;
/*
* Shut down temp installation's postmaster
*/
if (temp_install && clean)
{
header(_("shutting down postmaster"));
stop_postmaster();
}
fclose(logfile);
/*
* Emit nice-looking summary message
*/
if (fail_count == 0 && fail_ignore_count == 0)
(void)snprintf(buf, sizeof(buf),
_(" All %d tests passed. "),
success_count);
else if (fail_count == 0) /* fail_count=0, fail_ignore_count>0 */
(void)snprintf(buf, sizeof(buf),
_(" %d of %d tests passed, %d failed test(s) ignored. "),
success_count,
success_count + fail_ignore_count,
fail_ignore_count);
else if (fail_ignore_count == 0) /* fail_count>0 && fail_ignore_count=0 */
(void)snprintf(buf, sizeof(buf),
_(" %d of %d tests failed. "),
fail_count,
success_count + fail_count);
else
/* fail_count>0 && fail_ignore_count>0 */
(void)snprintf(buf, sizeof(buf),
_(" %d of %d tests failed, %d of these failures ignored. "),
fail_count + fail_ignore_count,
success_count + fail_count + fail_ignore_count,
fail_ignore_count);
(void)putchar('\n');
for (i = strlen(buf); i > 0; i--)
{ (void)putchar('='); }
printf("\n%s\n", buf);
printf(" Total Time: %fs\n",total_time);
for (i = strlen(buf); i > 0; i--)
{ (void)putchar('='); }
(void)putchar('\n');
(void)putchar('\n');
if (file_size(difffilename) > 0)
{
printf(_("The differences that caused some tests to fail can be viewed in the\n"
"file \"%s\". A copy of the test summary that you see\n"
"above is saved in the file \"%s\".\n\n"),
difffilename, logfilename);
}
else
{
unlink(difffilename);
unlink(logfilename);
}
if (fail_count != 0)
{ exit_nicely(1); }
}
return 0;
}