From 9381683a804b17434399c0bffc280ffd371890e8 Mon Sep 17 00:00:00 2001 From: roman khapov Date: Mon, 22 Dec 2025 13:41:20 +0000 Subject: [PATCH v2 1/2] inproduce general polling functions for TAP tests There are polling logic across all TAP tests, one of the notable example is poll_query_until. This logic must be implemented in each test in case the test want to perform some polling actions. This patch introduces two new functions at Utils: - poll_until, that make polling general condition callback - poll_cmd_until, that uses poll_until and makes polling for background process results. Existed poll_query_until rewritten to use more general poll_cmd_until, both new functions will be used in next patch, to implement all the waitings in tests for archive_mode=follow_primary feature. Author: Roman Khapov Reviewed-by: Discussion: --- src/test/perl/PostgreSQL/Test/Cluster.pm | 41 +------ src/test/perl/PostgreSQL/Test/Utils.pm | 146 +++++++++++++++++++++++ 2 files changed, 152 insertions(+), 35 deletions(-) diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm index 295988b8b87..e9add0c6660 100644 --- a/src/test/perl/PostgreSQL/Test/Cluster.pm +++ b/src/test/perl/PostgreSQL/Test/Cluster.pm @@ -2726,7 +2726,7 @@ sub poll_query_until { my ($self, $dbname, $query, $expected) = @_; - local %ENV = $self->_get_env(); + my %env = $self->_get_env(); $expected = 't' unless defined($expected); # default value @@ -2736,41 +2736,12 @@ sub poll_query_until '--dbname' => $self->connstr($dbname) ]; my ($stdout, $stderr); - my $max_attempts = 10 * $PostgreSQL::Test::Utils::timeout_default; - my $attempts = 0; - while ($attempts < $max_attempts) - { - my $result = IPC::Run::run $cmd, - '<' => \$query, - '>' => \$stdout, - '2>' => \$stderr; - - chomp($stdout); - chomp($stderr); - - if ($stdout eq $expected && $stderr eq '') - { - return 1; - } - - # Wait 0.1 second before retrying. - usleep(100_000); - - $attempts++; - } - - # Give up. Print the output from the last attempt, hopefully that's useful - # for debugging. - diag qq(poll_query_until timed out executing this query: -$query -expecting this output: -$expected -last actual query output: -$stdout -with stderr: -$stderr); - return 0; + return PostgreSQL::Test::Utils::poll_cmd($cmd, + input => $query, + expected => $expected, + env => \%env + ); } =pod diff --git a/src/test/perl/PostgreSQL/Test/Utils.pm b/src/test/perl/PostgreSQL/Test/Utils.pm index 0332d28916e..4f247b28f1b 100644 --- a/src/test/perl/PostgreSQL/Test/Utils.pm +++ b/src/test/perl/PostgreSQL/Test/Utils.pm @@ -57,6 +57,7 @@ use File::stat qw(stat); use File::Temp (); use IPC::Run; use POSIX qw(locale_h); +use Time::HiRes qw(usleep); use PostgreSQL::Test::SimpleTee; # We need a version of Test::More recent enough to support subtests @@ -562,6 +563,151 @@ sub append_to_file close $fh; return; } +=pod + +=item poll_until($condition, [, %options]) + +Run callback B<$condition> repeatedly, until it returns 1 +Continues polling if B<$condition> returns an error result. +Times out after $PostgreSQL::Test::Utils::timeout_default seconds. +Returns 1 if successful, 0 if timed out. + +=over + +=item interval => $number + +Number of microseconds to wait before next poll. +Default is 100_000 (0.1 second). + +=item max_attempts => $number + +Maximum number of polling attempts. +Default is B<$PostgreSQL::Test::Utils::timeout_default> / B<$interval>. + +=back + +=cut + +sub poll_until +{ + my ($condition, %args) = @_; + + my $interval = $args{interval} // 100_000; + my $max_attempts = $args{max_attempts} // int($timeout_default * 1_000_000 / $interval); + + my $attempts = 0; + while ($attempts < $max_attempts) + { + if ($condition->()) + { + return 1; + } + + usleep($interval); + + $attempts++; + } + + return 0; +} + +=pod + +=item poll_cmd($cmd, %options) + +Run B<$cmd> repeatedly until it returns the expected result. +Continues polling if B<$cmd> returns an error result. +Times out after B<$PostgreSQL::Test::Utils::timeout_default> seconds. +Returns 1 if successful, 0 if timed out. + +=over + +=item expected => $string + +Expected stdout output. If not provided, checks for zero return code. + +=item input => $string + +Optional input to pass to the command via stdin. + +=item interval => $number + +Number of microseconds to wait before next poll. +Default is 100_000 (0.1 second). + +=item max_attempts => $number + +Maximum number of polling attempts. +Default is B<$PostgreSQL::Test::Utils::timeout_default> / B<$interval>. + +=item env => \%hash + +Hash reference with environment variables to set for the command. + +=back + +=cut + +sub poll_cmd +{ + my ($cmd, %args) = @_; + + my $expected = $args{expected}; + my $input = $args{input}; + my $env = $args{env}; + my $max_attempts = $args{max_attempts}; + my $interval = $args{interval}; + + my ($stdout, $stderr); + + local %ENV = %$env if defined($env); + + my $condition = sub { + my $result; + + $result = IPC::Run::run $cmd, + '<' => \$input, + '>' => \$stdout, + '2>' => \$stderr; + + chomp($stdout); + chomp($stderr); + + if (defined($expected)) + { + return ($stdout eq $expected && $stderr eq ''); + } + else + { + return $result; + } + }; + + my %poll_args = (); + $poll_args{max_attempts} = $max_attempts if defined($max_attempts); + $poll_args{interval} = $interval if defined($interval); + + my $result = poll_until($condition, %poll_args); + + if (!$result) + { + my $cmd_str = join(' ', @$cmd); + my $expected_str = defined($expected) ? $expected : ''; + + diag qq(poll_cmd timed out executing this cmd: +$cmd_str +with input: +$input +expecting this output: +$expected_str +last actual cmd output: +$stdout +with stderr: +$stderr); + } + + return $result; +} =pod -- 2.43.0