aboutsummaryrefslogtreecommitdiff
path: root/src/test/isolation/isolationtester.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/test/isolation/isolationtester.c')
-rw-r--r--src/test/isolation/isolationtester.c372
1 files changed, 372 insertions, 0 deletions
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
new file mode 100644
index 00000000000..99460519456
--- /dev/null
+++ b/src/test/isolation/isolationtester.c
@@ -0,0 +1,372 @@
+/*
+ * src/test/isolation/isolationtester.c
+ *
+ * isolationtester.c
+ * Runs an isolation test specified by a spec file.
+ */
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "libpq-fe.h"
+
+#include "isolationtester.h"
+
+static PGconn **conns = NULL;
+static int nconns = 0;
+
+static void run_all_permutations(TestSpec *testspec);
+static void run_all_permutations_recurse(TestSpec *testspec, int nsteps, Step **steps);
+static void run_named_permutations(TestSpec *testspec);
+static void run_permutation(TestSpec *testspec, int nsteps, Step **steps);
+
+static int step_qsort_cmp(const void *a, const void *b);
+static int step_bsearch_cmp(const void *a, const void *b);
+
+static void printResultSet(PGresult *res);
+
+/* close all connections and exit */
+static void
+exit_nicely(void)
+{
+ int i;
+ for (i = 0; i < nconns; i++)
+ PQfinish(conns[i]);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ const char *conninfo;
+ TestSpec *testspec;
+ int i;
+
+ /*
+ * If the user supplies a parameter on the command line, use it as the
+ * conninfo string; otherwise default to setting dbname=postgres and
+ * using environment variables or defaults for all other connection
+ * parameters.
+ */
+ if (argc > 1)
+ conninfo = argv[1];
+ else
+ conninfo = "dbname = postgres";
+
+ /* Read the test spec from stdin */
+ spec_yyparse();
+ testspec = &parseresult;
+ printf("Parsed test spec with %d sessions\n", testspec->nsessions);
+
+ /* Establish connections to the database, one for each session */
+ nconns = testspec->nsessions;
+ conns = calloc(nconns, sizeof(PGconn *));
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ PGresult *res;
+
+ conns[i] = PQconnectdb(conninfo);
+ if (PQstatus(conns[i]) != CONNECTION_OK)
+ {
+ fprintf(stderr, "Connection %d to database failed: %s",
+ i, PQerrorMessage(conns[i]));
+ exit_nicely();
+ }
+
+ /*
+ * Suppress NOTIFY messages, which otherwise pop into results at odd
+ * places.
+ */
+ res = PQexec(conns[i], "SET client_min_messages = warning;");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "message level setup failed: %s", PQerrorMessage(conns[i]));
+ exit_nicely();
+ }
+ PQclear(res);
+ }
+
+ /* Set the session index fields in steps. */
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ Session *session = testspec->sessions[i];
+ int stepindex;
+ for (stepindex = 0; stepindex < session->nsteps; stepindex++)
+ session->steps[stepindex]->session = i;
+ }
+
+ /*
+ * Run the permutations specified in the spec, or all if none were
+ * explicitly specified.
+ */
+ if (testspec->permutations)
+ run_named_permutations(testspec);
+ else
+ run_all_permutations(testspec);
+
+ /* Clean up and exit */
+ for (i = 0; i < nconns; i++)
+ PQfinish(conns[i]);
+ return 0;
+}
+
+static int *piles;
+
+/*
+ * Run all permutations of the steps and sessions.
+ */
+static void
+run_all_permutations(TestSpec *testspec)
+{
+ int nsteps;
+ int i;
+ Step **steps;
+
+ /* Count the total number of steps in all sessions */
+ nsteps = 0;
+ for (i = 0; i < testspec->nsessions; i++)
+ nsteps += testspec->sessions[i]->nsteps;
+
+ steps = malloc(sizeof(Step *) * nsteps);
+
+ /*
+ * To generate the permutations, we conceptually put the steps of
+ * each session on a pile. To generate a permuation, we pick steps
+ * from the piles until all piles are empty. By picking steps from
+ * piles in different order, we get different permutations.
+ *
+ * A pile is actually just an integer which tells how many steps
+ * we've already picked from this pile.
+ */
+ piles = malloc(sizeof(int) * testspec->nsessions);
+ for (i = 0; i < testspec->nsessions; i++)
+ piles[i] = 0;
+
+ run_all_permutations_recurse(testspec, 0, steps);
+}
+
+static void
+run_all_permutations_recurse(TestSpec *testspec, int nsteps, Step **steps)
+{
+ int i;
+ int found = 0;
+
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ /* If there's any more steps in this pile, pick it and recurse */
+ if (piles[i] < testspec->sessions[i]->nsteps)
+ {
+ steps[nsteps] = testspec->sessions[i]->steps[piles[i]];
+ piles[i]++;
+
+ run_all_permutations_recurse(testspec, nsteps + 1, steps);
+
+ piles[i]--;
+
+ found = 1;
+ }
+ }
+
+ /* If all the piles were empty, this permutation is completed. Run it */
+ if (!found)
+ run_permutation(testspec, nsteps, steps);
+}
+
+/*
+ * Run permutations given in the test spec
+ */
+static void
+run_named_permutations(TestSpec *testspec)
+{
+ int i, j;
+ int n;
+ int nallsteps;
+ Step **allsteps;
+
+ /* First create a lookup table of all steps */
+ nallsteps = 0;
+ for (i = 0; i < testspec->nsessions; i++)
+ nallsteps += testspec->sessions[i]->nsteps;
+
+ allsteps = malloc(nallsteps * sizeof(Step *));
+
+ n = 0;
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ for (j = 0; j < testspec->sessions[i]->nsteps; j++)
+ allsteps[n++] = testspec->sessions[i]->steps[j];
+ }
+
+ qsort(allsteps, nallsteps, sizeof(Step *), &step_qsort_cmp);
+
+ for (i = 0; i < testspec->npermutations; i++)
+ {
+ Permutation *p = testspec->permutations[i];
+ Step **steps;
+
+ steps = malloc(p->nsteps * sizeof(Step *));
+
+ /* Find all the named steps from the lookup table */
+ for (j = 0; j < p->nsteps; j++)
+ {
+ steps[j] = *((Step **) bsearch(p->stepnames[j], allsteps, nallsteps,
+ sizeof(Step *), &step_bsearch_cmp));
+ if (steps[j] == NULL)
+ {
+ fprintf(stderr, "undefined step \"%s\" specified in permutation\n", p->stepnames[j]);
+ exit_nicely();
+ }
+ }
+
+ run_permutation(testspec, p->nsteps, steps);
+ free(steps);
+ }
+}
+
+static int
+step_qsort_cmp(const void *a, const void *b)
+{
+ Step *stepa = *((Step **) a);
+ Step *stepb = *((Step **) b);
+
+ return strcmp(stepa->name, stepb->name);
+}
+
+static int
+step_bsearch_cmp(const void *a, const void *b)
+{
+ char *stepname = (char *) a;
+ Step *step = *((Step **) b);
+
+ return strcmp(stepname, step->name);
+}
+
+/*
+ * Run one permutation
+ */
+static void
+run_permutation(TestSpec *testspec, int nsteps, Step **steps)
+{
+ PGresult *res;
+ int i;
+
+ printf("\nstarting permutation:");
+ for (i = 0; i < nsteps; i++)
+ printf(" %s", steps[i]->name);
+ printf("\n");
+
+ /* Perform setup */
+ if (testspec->setupsql)
+ {
+ res = PQexec(conns[0], testspec->setupsql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "setup failed: %s", PQerrorMessage(conns[0]));
+ exit_nicely();
+ }
+ PQclear(res);
+ }
+
+ /* Perform per-session setup */
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ if (testspec->sessions[i]->setupsql)
+ {
+ res = PQexec(conns[i], testspec->sessions[i]->setupsql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "setup of session %s failed: %s",
+ testspec->sessions[i]->name,
+ PQerrorMessage(conns[0]));
+ exit_nicely();
+ }
+ PQclear(res);
+ }
+ }
+
+ /* Perform steps */
+ for (i = 0; i < nsteps; i++)
+ {
+ Step *step = steps[i];
+ printf("step %s: %s\n", step->name, step->sql);
+ res = PQexec(conns[step->session], step->sql);
+
+ switch(PQresultStatus(res))
+ {
+ case PGRES_COMMAND_OK:
+ break;
+
+ case PGRES_TUPLES_OK:
+ printResultSet(res);
+ break;
+
+ case PGRES_FATAL_ERROR:
+ /* Detail may contain xid values, so just show primary. */
+ printf("%s: %s\n", PQresultErrorField(res, PG_DIAG_SEVERITY),
+ PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY));
+ break;
+
+ default:
+ printf("unexpected result status: %s\n",
+ PQresStatus(PQresultStatus(res)));
+ }
+ PQclear(res);
+ }
+
+ /* Perform per-session teardown */
+ for (i = 0; i < testspec->nsessions; i++)
+ {
+ if (testspec->sessions[i]->teardownsql)
+ {
+ res = PQexec(conns[i], testspec->sessions[i]->teardownsql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "teardown of session %s failed: %s",
+ testspec->sessions[i]->name,
+ PQerrorMessage(conns[0]));
+ /* don't exit on teardown failure */
+ }
+ PQclear(res);
+ }
+ }
+
+ /* Perform teardown */
+ if (testspec->teardownsql)
+ {
+ res = PQexec(conns[0], testspec->teardownsql);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ {
+ fprintf(stderr, "teardown failed: %s",
+ PQerrorMessage(conns[0]));
+ /* don't exit on teardown failure */
+
+ }
+ PQclear(res);
+ }
+}
+
+static void
+printResultSet(PGresult *res)
+{
+ int nFields;
+ int i, j;
+
+ /* first, print out the attribute names */
+ nFields = PQnfields(res);
+ for (i = 0; i < nFields; i++)
+ printf("%-15s", PQfname(res, i));
+ printf("\n\n");
+
+ /* next, print out the rows */
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ for (j = 0; j < nFields; j++)
+ printf("%-15s", PQgetvalue(res, i, j));
+ printf("\n");
+ }
+}