Finally, I added new TAP test library PsqlSession.

At Tue, 18 Jul 2017 18:12:13 +0900 (Tokyo Standard Time), Kyotaro HORIGUCHI 
<horiguchi.kyot...@lab.ntt.co.jp> wrote in 
<20170718.181213.206979369.horiguchi.kyot...@lab.ntt.co.jp>
> > * How about some regression test cases?  You couldn't really exercise
> > cross-session invalidation easily, but I don't think you need to.
> 
> Ha Ha. You got me. I will add some test cases for this in the
> next version. Thanks.

Here it is. First I tried this using ordinary regression
framework but the effect of this patch is shown only in log and
it contains variable parts so I gave up it before trying more
complex way.

Next I tried existing TAP test but this test needs continuous
session to achieve alternating operation on two sessions but
PostgresNode::psql doesn't offer such a functionality.

Finally, I added a new TAP test library PsqlSession. It offers
interactive psql sessions. Then added a simple test to
postgres_fdw using it.

The first patch is the PsqlSession.pm and the second is the new
test for postgres_fdw.

- The current PsqlSession is quite fragile but seems working
  enough for this usage at the present.

- I'm afraid this might not work on Windows according to the
  manpage of IPC::Run, but I haven't confirmed yet.

  http://search.cpan.org/~toddr/IPC-Run-0.96/lib/IPC/Run.pm#Win32_LIMITATIONS


Any comment or suggestions are welcome.

regards,

-- 
Kyotaro Horiguchi
NTT Open Source Software Center
>From fdb5cbab3375d9d2e4da078cf6ee7eaf7de5c8fd Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp>
Date: Thu, 20 Jul 2017 14:56:51 +0900
Subject: [PATCH 1/2] Add new TAP test library PsqlSession.pm

PostgreNode::psql makes temporary session to run commands so it is not
usable when more interactive operation on a continued session. This
library offers continuous sessions feature that can execute multiple
sql commands separately.
---
 src/test/perl/PsqlSession.pm | 341 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 341 insertions(+)
 create mode 100644 src/test/perl/PsqlSession.pm

diff --git a/src/test/perl/PsqlSession.pm b/src/test/perl/PsqlSession.pm
new file mode 100644
index 0000000..d69fd14
--- /dev/null
+++ b/src/test/perl/PsqlSession.pm
@@ -0,0 +1,341 @@
+
+=pod
+
+=head1 NAME
+
+PsqlSession - class representing PostgreSQL psql instance
+
+=head1 SYNOPSIS
+
+  use PsqlSession;
+
+  my $session = get_new_session('session1', $server);
+
+  to connect to a PostgreNode as $server, or
+
+  my $session = get_new_session('session1', 'localhost', '5432', 'postgres');
+
+  to specify each options explicitly.
+
+  # Connect to the server
+  $session->open();
+
+  # Execute an SQL query
+  $ret = $session->execsql('SELECT now();');
+
+  Returns a pair of output of stdout, and stderr in array context.
+
+  ($out, $err) = $session->execsql('SELECT now();');
+
+  $session->execsql_multi('SELECT 1;', 'SELECT now();');
+
+  is just a shortcut of writing many execsqls.
+
+  # End the session
+  $session->close();
+
+=head1 DESCRIPTION
+
+PsqlSession contains a set of routines able to work on a psql session,
+allowing to connect, send a command and receive the result and close.
+
+The IPC::Run module is required.
+
+=cut
+
+package PsqlSession;
+
+use strict;
+use warnings;
+
+use Exporter 'import';
+use Test::More;
+use TestLib ();
+use Scalar::Util qw(blessed);
+
+our @EXPORT = qw(
+  get_new_session
+);
+
+
+=pod
+
+=head1 METHODS
+
+=over
+
+=item PsqlSession::new($class, $name, $pghost, $pgport, $dbname)
+
+Create a new PsqlSession instance. Does not connect.
+
+You should generally prefer to use get_new_session() instead since it
+takes care of finding host name, port number or database name.
+
+=cut
+
+sub new
+{
+	my ($class, $name, $pghost, $pgport, $dbname) = @_;
+
+	my $self = {
+		_name => $name,
+		_host => $pghost,
+		_port => $pgport,
+		_dbname => $dbname };
+
+	bless $self, $class;
+
+#	$self->dump_info;
+
+	return $self;
+}
+
+=pod
+
+=item $session->name()
+
+The name assigned to the session at creation time.
+
+=cut
+
+sub name
+{
+	return $_[0]->{_name};
+}
+
+=pod
+
+=item $session->host()
+
+Return the host (like PGHOST) for this instance. May be a UNIX socket path.
+
+=cut
+
+sub host
+{
+	return $_[0]->{_host};
+}
+
+=pod
+
+=item $session->port()
+
+Get the port number connects to. This won't necessarily be a TCP port
+open on the local host since we prefer to use unix sockets if
+possible.
+
+=cut
+
+sub port
+{
+	return $_[0]->{_port};
+}
+
+=pod
+
+=item $session->dbname()
+
+Get the database name this session connects to.
+
+=cut
+
+sub dbname
+{
+	return $_[0]->{_dbname};
+}
+
+=pod
+
+=item $session->errstate()
+
+Get the error state of this session. 0 means no error and 1 means
+error. This value is reset at the starting of every execution of an
+SQL query.
+
+=cut
+
+sub errstate
+{
+	return $_[0]->{_errstate};
+}
+
+=pod
+
+=item $session->open()
+
+Open this session.
+
+=cut
+
+sub open
+{
+	my ($self) = @_;
+
+	# Create anonymous scalar references to be passed to IPC::Run::start
+	my $in = do {\my $anon};
+	my $out = do {\my $anon};
+	my $err = do {\my $anon};
+
+	# create and store the harness
+	$self->{_run} = IPC::Run::start
+		["psql", $self->dbname, "-p" ,$self->port, "-h", $self->host, "-n"],
+		$in, $out, $err, IPC::Run::timeout(10);
+
+	$self->{_in} = $in;
+	$self->{_out} = $out;
+	$self->{_err} = $err;
+	$self->{_errstate} = 0;
+}
+
+=pod
+
+=item $session->clearerr()
+
+Clear error status.
+
+=cut
+
+sub clearerr
+{
+	$_[0]->errstate = 0;
+}
+
+=pod
+
+=item $session->close()
+
+Close this session. This session can be relaunched by open().
+
+=cut
+
+sub close
+{
+	my ($self) = @_;
+
+	if (defined $self->{_run})
+	{
+		IPC::Run::finish($self->{_run});
+		delete $self->{_run};
+	}
+}
+
+=pod
+
+=item $session->execsql($sqlstr)
+
+Execute sql commands in $sqlstr then returns the
+result. Multistatement is allowed.
+
+In scalar context, this function returns a string that contains the
+result of the query. In array context, returns an array consists of
+the output of stdout and stderr.
+
+For implment reasons, this function adds a sentinel query that gives a
+end marker for reading the result at the end of the query. The stderr
+may contain the log for the sentinel query on certain setting, such as
+log_min_duration_statement = 0.
+
+If the stderr contained the string 'ERROR:', this session is marked as
+error. This state can be checked by $session->errstate();
+
+=cut
+
+sub execsql
+{
+	my ($self, $cmd) = @_;
+	my $run = $self->{_run};
+
+	die if (!defined $run);
+	${$self->{_out}} = '';
+	${$self->{_err}} = '';
+
+	# add a query to receive an end marker
+	$cmd =~ s/[;\n]+$//;
+	${$self->{_in}} .= $cmd.";select '###END###' as end;\n";
+
+	# wait for the end marker or an error
+	$self->{_run}->pump until
+		(${$self->{_out}} =~ /###END###\n\(1 row\)\n/ ||
+		 ${$self->{_err}} =~ /ERROR:/);
+
+	# set error state
+	$self->{_errstate} = 1 if (${$self->{_err}} =~ /ERROR:/);
+
+	# remove useless result
+	my $out = ${$self->{_out}};
+	$out =~ s/ *end *\n-+\n *###END### *\n\(1 row\)\n\n//g;
+
+	return wantarray ? ($out, ${$self->{_err}}) : $out;
+}
+
+=pod
+
+=item $session->execsql_multi("query", "query",...)
+
+Run multiple queries. This is just a convenient function using instead
+of a series of execsql(). Returns concatenated output of each
+query. Returns the output of stdout in scalar context and a pair of
+stdout and stderr in array context.
+
+If any query fails, this function returns immediately.
+
+=cut
+
+sub execsql_multi
+{
+	my ($self) = shift(@_);
+	my ($retout, $reterr, $out, $err);
+
+	foreach my $cmd (@_)
+    {
+		($out, $err) = $self->execsql($cmd);
+		$retout .= $out;
+		$reterr .= $err;
+		last if ($self->errstate);
+	}
+
+	return wantarray ? ($retout, $reterr) : $retout;
+}
+
+=pod
+
+=item get_new_sesion($name, $host, $port, $dbname) or
+     get_new_sesion($name, $PostgreNode)
+
+Build a new session object to connect to the specified
+server. Standalone function that's automatically imported.
+
+In the first form, returns a session according to the parameters. In
+the second form, returns a session connects to the node.
+
+=cut
+
+
+sub get_new_session
+{
+	my ($name, $host_or_ref, $pgport, $dbname) = @_;
+	my $pghost = $host_or_ref;
+
+	if (blessed($host_or_ref) && $host_or_ref->isa('PostgresNode'))
+	{
+		$pghost = $host_or_ref->host;
+		$pgport = $host_or_ref->port;
+	}
+
+	# Fill in default values
+	$pghost = 'localhost' if (! defined $pghost);
+	$pgport = 5432 if (! defined $pgport);
+	$dbname = 'postgres' if (! defined $dbname);
+
+	my $session = new PsqlSession($name, $pghost, $pgport, $dbname);
+
+	return $session;
+}
+
+=pod
+
+=back
+
+=cut
+
+1;
-- 
2.9.2

>From c47a3b348c5818e341668b074582eef6b23376ef Mon Sep 17 00:00:00 2001
From: Kyotaro Horiguchi <horiguchi.kyot...@lab.ntt.co.jp>
Date: Thu, 20 Jul 2017 15:02:12 +0900
Subject: [PATCH 2/2] Add test for reconnection feature

This patch adds the regression test for the session reconnection
feature of postgres_fdw. make prove-check runs the test.
---
 contrib/postgres_fdw/Makefile              |  3 ++
 contrib/postgres_fdw/t/001_reconnection.pl | 65 ++++++++++++++++++++++++++++++
 2 files changed, 68 insertions(+)
 create mode 100644 contrib/postgres_fdw/t/001_reconnection.pl

diff --git a/contrib/postgres_fdw/Makefile b/contrib/postgres_fdw/Makefile
index 3543312..1be5c9f 100644
--- a/contrib/postgres_fdw/Makefile
+++ b/contrib/postgres_fdw/Makefile
@@ -23,3 +23,6 @@ top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 include $(top_srcdir)/contrib/contrib-global.mk
 endif
+
+prove-check:
+	$(prove_check)
diff --git a/contrib/postgres_fdw/t/001_reconnection.pl b/contrib/postgres_fdw/t/001_reconnection.pl
new file mode 100644
index 0000000..b93725d
--- /dev/null
+++ b/contrib/postgres_fdw/t/001_reconnection.pl
@@ -0,0 +1,65 @@
+# Minimal test testing reconnection
+use strict;
+use warnings;
+use PostgresNode;
+use PsqlSession;
+use TestLib;
+use Test::More tests => 6;
+
+# start a server
+my $server = get_new_node('server');
+$server->init();
+$server->start;
+my $session1 = get_new_session('session1', $server);
+my $session2 = get_new_session('session2', $server);
+$session1->open;
+$session2->open;
+
+my $ret = $session1->execsql("SELECT current_database()");
+ok($ret =~ /^.*\n-.*-\n *(\S+) *\n/,
+   'retrieving connection setting');
+my $dbname = $1;
+my $port = $server->port;
+
+$session1->execsql_multi(
+	("CREATE EXTENSION postgres_fdw;",
+	 "CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw OPTIONS (dbname \'$dbname\', port \'$port\');",
+	"CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;",
+	"CREATE TABLE lt1 (c1 int);",
+	"INSERT INTO lt1 VALUES (1);",
+	"CREATE FOREIGN TABLE ft1 (c1 int) SERVER loopback OPTIONS (table_name 'lt1');",
+	 "SET client_min_messages to DEBUG3;"));
+ok($session1->errstate == 0, 'setting up');
+
+$session1->execsql("BEGIN;");
+
+my ($stout, $sterr) = $session1->execsql("SELECT c1 FROM ft1 LIMIT 1;");
+ok($sterr =~ /DEBUG: *new postgres_fdw connection 0x[[:xdigit:]]+/,
+   "creating new connection");
+
+$session2->execsql_multi(
+	"ALTER SERVER loopback OPTIONS (ADD host 'hoge')",
+	"ALTER SERVER loopback OPTIONS (DROP host)");
+
+($stout, $sterr) =  $session1->execsql("SELECT c1 FROM ft1 LIMIT 1;");
+ok($sterr !~ /DEBUG: *closing connection 0x[[:xdigit:]]+ for option changes to take effect/,
+   'no disconnection within a transaction');
+
+$session1->execsql("COMMIT;");
+
+$session2->execsql_multi(
+	"ALTER USER MAPPING ft1 OPTIONS (host 'hoge')",
+	"ALTER SERVER loopback OPTIONS (DROP host)");
+
+
+($stout, $sterr) = $session1->execsql("SELECT c1 FROM ft1 LIMIT 1;");
+ok($sterr =~ /DEBUG: *closing connection 0x[[:xdigit:]]+ for option changes to take effect\nDEBUG: *new postgres_fdw connection 0x[[:xdigit:]]+/,
+   'reconnection by option change outside a transactin');
+
+($stout, $sterr) =  $session1->execsql("SELECT c1 FROM ft1 LIMIT 1;");
+ok($sterr !~ /DEBUG: *closing connection 0x[[:xdigit:]]+ for option changes to take effect/,
+   'no disconnection without option change');
+
+$session1->close;
+$session2->close;
+$server->stop;
-- 
2.9.2

-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to