From e2ab8832fa476b14b44f7452985b1b553bd44b6a Mon Sep 17 00:00:00 2001 From: Thomas Munro Date: Sat, 18 Apr 2020 16:16:06 +1200 Subject: [PATCH v2 6/6] Add TAP test for snapshot-too-old. --- contrib/old_snapshot/t/002_too_old.pl | 75 ++++++++++++++++ src/test/perl/PsqlSession.pm | 122 ++++++++++++++++++++++++++ 2 files changed, 197 insertions(+) create mode 100644 contrib/old_snapshot/t/002_too_old.pl create mode 100644 src/test/perl/PsqlSession.pm diff --git a/contrib/old_snapshot/t/002_too_old.pl b/contrib/old_snapshot/t/002_too_old.pl new file mode 100644 index 0000000000..097a464763 --- /dev/null +++ b/contrib/old_snapshot/t/002_too_old.pl @@ -0,0 +1,75 @@ +# Simple test of early pruning and snapshot-too-old errors. +use strict; +use warnings; +use PostgresNode; +use PsqlSession; +use TestLib; +use Test::More tests => 5; + +my $node = get_new_node('master'); +$node->init; +$node->append_conf("postgresql.conf", "timezone = UTC"); +$node->append_conf("postgresql.conf", "old_snapshot_threshold=10"); +$node->append_conf("postgresql.conf", "max_prepared_transactions=10"); +$node->start; +$node->psql('postgres', 'create extension old_snapshot'); +$node->psql('postgres', 'create table t (i int)'); +$node->psql('postgres', 'insert into t select generate_series(1, 42)'); + +# start an interactive session that we can use to interleave statements +my $session = PsqlSession->new($node, "postgres"); +$session->send("\\set PROMPT1 ''\n", 2); +$session->send("\\set PROMPT2 ''\n", 1); + +my @lines; +my $command_tag; +my $result; + +# begin a session that we can interleave with vacuum activity +@lines = $session->send("begin transaction isolation level repeatable read;\n", 2); +shift @lines; +$command_tag = shift @lines; +is($command_tag, "BEGIN"); + +# take a snapshot at time 0 +$node->psql('postgres', "select pg_clobber_current_snapshot_timestamp('3000-01-01 00:00:00Z')"); +@lines = $session->send("select * from t order by i limit 1;\n", 2); +shift @lines; +$result = shift @lines; +is($result, "1"); + +# advance time by 10 minutes, then UPDATE and VACUUM the table +$node->psql('postgres', "select pg_clobber_current_snapshot_timestamp('3000-01-01 00:10:00Z')"); +$node->psql('postgres', "update t set i = 1001 where i = 1"); +$node->psql('postgres', "vacuum analyze t"); + +# our snapshot is not too old yet, so we can still use it +@lines = $session->send("select * from t order by i limit 1;\n", 2); +shift @lines; +$result = shift @lines; +is($result, "1"); + +# advance time by 10 more minutes, then UPDATE and VACUUM the table +$node->psql('postgres', "select pg_clobber_current_snapshot_timestamp('3000-01-01 00:20:00Z')"); +$node->psql('postgres', "update t set i = 1001 where i = 1"); +$node->psql('postgres', "vacuum analyze t"); + +# our snapshot is not too old yet, so we can still use it +@lines = $session->send("select * from t order by i limit 1;\n", 2); +shift @lines; +$result = shift @lines; +is($result, "1"); + +# advance time by just one more minute, then UPDATE and VACUUM the table +$node->psql('postgres', "select pg_clobber_current_snapshot_timestamp('3000-01-01 00:21:00Z')"); +$node->psql('postgres', "update t set i = 1002 where i = 1"); +$node->psql('postgres', "vacuum analyze t"); + +# our snapshot is too old! the thing it wants to see has been removed +@lines = $session->send("select * from t order by i limit 1;\n", 2); +shift @lines; +$result = shift @lines; +is($result, "ERROR: snapshot too old"); + +$session->close; +$node->stop; diff --git a/src/test/perl/PsqlSession.pm b/src/test/perl/PsqlSession.pm new file mode 100644 index 0000000000..b34dcca502 --- /dev/null +++ b/src/test/perl/PsqlSession.pm @@ -0,0 +1,122 @@ +=pod + +=head1 NAME + +PsqlSession - class representing psql connection + +=head1 SYNOPSIS + + use PsqlSession; + + my $node = PostgresNode->get_new_node('mynode'); + my $session = PsqlSession->new($node, "dbname"); + + # send simple query and wait for one line response + my $result = $session->send("SELECT 42;", 1); + + # close connection + $session->close(); + +=head1 DESCRIPTION + +PsqlSession allows for tests of interleaved operations, similar to +isolation tests. + +=cut + +package PsqlSession; + +use strict; +use warnings; + +use PostgresNode; +use TestLib; +use IPC::Run qw(pump finish timer); + +our @EXPORT = qw( + new + send + close +); + +=pod + +=head1 METHODS + +=over + +=item PsqlSession::new($class, $node, $dbname) + +Create a new PsqlSession instance, connected to a database. + +=cut + +sub new +{ + my ($class, $node, $dbname) = @_; + my $timer = timer(5); + my $stdin = ''; + my $stdout = ''; + my $harness = $node->interactive_psql($dbname, \$stdin, \$stdout, $timer); + my $self = { + _harness => $harness, + _stdin => \$stdin, + _stdout => \$stdout, + _timer => $timer + }; + bless $self, $class; + return $self; +} + +=pod + +=item $session->send($input, $lines) + +Send the given input to psql, and then wait for the given number of lines +of output, or a timeout. + +=cut + +sub count_lines +{ + my ($s) = @_; + return $s =~ tr/\n//; +} + +sub send +{ + my ($self, $statement, $lines) = @_; + ${$self->{_stdout}} = ''; + ${$self->{_stdin}} .= $statement; + $self->{_timer}->start(5); + pump $self->{_harness} until count_lines(${$self->{_stdout}}) == $lines || $self->{_timer}->is_expired; + die "expected ${lines} lines but after timeout, received only: ${$self->{_stdout}}" if $self->{_timer}->is_expired; + my @result = split /\n/, ${$self->{_stdout}}; + chop(@result); + return @result; +} + +=pod + +=item $session->close() + +Close a PsqlSession connection. + +=cut + +sub close +{ + my ($self) = @_; + $self->{_timer}->start(5); + ${$self->{_stdin}} .= "\\q\n"; + finish $self->{_harness} or die "psql returned $?"; + $self->{_timer}->reset; +} + +=pod + +=back + +=cut + +1; -- 2.20.1