/*-------------------------------------------------------------------------
 *
 * pg_regress_ecpg --- regression test driver for ecpg
 *
 * 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-2021, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/interfaces/ecpg/test/pg_regress_ecpg.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include "pg_regress.h"
#include "common/string.h"
#include "lib/stringinfo.h"


/*
 * Create a filtered copy of sourcefile, removing any path
 * appearing in #line directives; for example, replace
 * #line x "./../bla/foo.h" with #line x "foo.h".
 * This is needed because the path part can vary depending
 * on compiler, platform, build options, etc.
 */
static void
ecpg_filter_source(const char *sourcefile, const char *outfile)
{
	FILE	   *s,
			   *t;
	StringInfoData linebuf;

	s = fopen(sourcefile, "r");
	if (!s)
	{
		fprintf(stderr, "Could not open file %s for reading\n", sourcefile);
		exit(2);
	}
	t = fopen(outfile, "w");
	if (!t)
	{
		fprintf(stderr, "Could not open file %s for writing\n", outfile);
		exit(2);
	}

	initStringInfo(&linebuf);

	while (pg_get_line_buf(s, &linebuf))
	{
		/* check for "#line " in the beginning */
		if (strstr(linebuf.data, "#line ") == linebuf.data)
		{
			char	   *p = strchr(linebuf.data, '"');
			int			plen = 1;

			while (*p && (*(p + plen) == '.' || strchr(p + plen, '/') != NULL))
			{
				plen++;
			}
			/* plen is one more than the number of . and / characters */
			if (plen > 1)
			{
				memmove(p + 1, p + plen, strlen(p + plen) + 1);
				/* we don't bother to fix up linebuf.len */
			}
		}
		fputs(linebuf.data, t);
	}

	pfree(linebuf.data);
	fclose(s);
	fclose(t);
}

/*
 * Remove the details of connection failure error messages
 * in a test result file, since the target host/pathname and/or port
 * can vary.  Rewrite the result file in-place.
 *
 * At some point it might be interesting to unify this with
 * ecpg_filter_source, but building a general pattern matcher
 * is no fun, nor does it seem desirable to introduce a
 * dependency on an external one.
 */
static void
ecpg_filter_stderr(const char *resultfile, const char *tmpfile)
{
	FILE	   *s,
			   *t;
	StringInfoData linebuf;

	s = fopen(resultfile, "r");
	if (!s)
	{
		fprintf(stderr, "Could not open file %s for reading\n", resultfile);
		exit(2);
	}
	t = fopen(tmpfile, "w");
	if (!t)
	{
		fprintf(stderr, "Could not open file %s for writing\n", tmpfile);
		exit(2);
	}

	initStringInfo(&linebuf);

	while (pg_get_line_buf(s, &linebuf))
	{
		char	   *p1 = strstr(linebuf.data, "connection to server ");

		if (p1)
		{
			char	   *p2 = strstr(p1, "failed: ");

			if (p2)
			{
				memmove(p1 + 21, p2, strlen(p2) + 1);
				/* we don't bother to fix up linebuf.len */
			}
		}
		fputs(linebuf.data, t);
	}

	pfree(linebuf.data);
	fclose(s);
	fclose(t);
	if (rename(tmpfile, resultfile) != 0)
	{
		fprintf(stderr, "Could not overwrite file %s with %s\n",
				resultfile, tmpfile);
		exit(2);
	}
}

/*
 * start an ecpg test process for specified file (including redirection),
 * and return process ID
 */

static PID_TYPE
ecpg_start_test(const char *testname,
				_stringlist **resultfiles,
				_stringlist **expectfiles,
				_stringlist **tags)
{
	PID_TYPE	pid;
	char		inprg[MAXPGPATH];
	char		insource[MAXPGPATH];
	StringInfoData testname_dash;
	char		outfile_stdout[MAXPGPATH],
				expectfile_stdout[MAXPGPATH];
	char		outfile_stderr[MAXPGPATH],
				expectfile_stderr[MAXPGPATH];
	char		outfile_source[MAXPGPATH],
				expectfile_source[MAXPGPATH];
	char		cmd[MAXPGPATH * 3];
	char	   *appnameenv;

	snprintf(inprg, sizeof(inprg), "%s/%s", inputdir, testname);
	snprintf(insource, sizeof(insource), "%s.c", testname);

	initStringInfo(&testname_dash);
	appendStringInfoString(&testname_dash, testname);
	replace_string(&testname_dash, "/", "-");

	snprintf(expectfile_stdout, sizeof(expectfile_stdout),
			 "%s/expected/%s.stdout",
			 outputdir, testname_dash.data);
	snprintf(expectfile_stderr, sizeof(expectfile_stderr),
			 "%s/expected/%s.stderr",
			 outputdir, testname_dash.data);
	snprintf(expectfile_source, sizeof(expectfile_source),
			 "%s/expected/%s.c",
			 outputdir, testname_dash.data);

	snprintf(outfile_stdout, sizeof(outfile_stdout),
			 "%s/results/%s.stdout",
			 outputdir, testname_dash.data);
	snprintf(outfile_stderr, sizeof(outfile_stderr),
			 "%s/results/%s.stderr",
			 outputdir, testname_dash.data);
	snprintf(outfile_source, sizeof(outfile_source),
			 "%s/results/%s.c",
			 outputdir, testname_dash.data);

	add_stringlist_item(resultfiles, outfile_stdout);
	add_stringlist_item(expectfiles, expectfile_stdout);
	add_stringlist_item(tags, "stdout");

	add_stringlist_item(resultfiles, outfile_stderr);
	add_stringlist_item(expectfiles, expectfile_stderr);
	add_stringlist_item(tags, "stderr");

	add_stringlist_item(resultfiles, outfile_source);
	add_stringlist_item(expectfiles, expectfile_source);
	add_stringlist_item(tags, "source");

	ecpg_filter_source(insource, outfile_source);

	snprintf(cmd, sizeof(cmd),
			 "\"%s\" >\"%s\" 2>\"%s\"",
			 inprg,
			 outfile_stdout,
			 outfile_stderr);

	appnameenv = psprintf("ecpg/%s", testname_dash.data);
	setenv("PGAPPNAME", appnameenv, 1);
	free(appnameenv);

	pid = spawn_process(cmd);

	if (pid == INVALID_PID)
	{
		fprintf(stderr, _("could not start process for test %s\n"),
				testname);
		exit(2);
	}

	unsetenv("PGAPPNAME");

	free(testname_dash.data);

	return pid;
}

static void
ecpg_postprocess_result(const char *filename)
{
	int			nlen = strlen(filename);

	/* Only stderr files require filtering, at the moment */
	if (nlen > 7 && strcmp(filename + nlen - 7, ".stderr") == 0)
	{
		char	   *tmpfile = psprintf("%s.tmp", filename);

		ecpg_filter_stderr(filename, tmpfile);
		pfree(tmpfile);
	}
}

static void
ecpg_init(int argc, char *argv[])
{
	/* nothing to do here at the moment */
}

int
main(int argc, char *argv[])
{
	return regression_main(argc, argv,
						   ecpg_init,
						   ecpg_start_test,
						   ecpg_postprocess_result);
}
