Skip to content

Commit bb2bf22

Browse files
committed
tests: BackgroundPsql: Fix potential for lost errors on windows
This addresses various corner cases in BackgroundPsql: - On windows stdout and stderr may arrive out of order, leading to errors not being reported, or attributed to the wrong statement. To fix, emit the "query-separation banner" on both stdout and stderr and wait for both. - Very occasionally the "query-separation banner" would not get removed, because we waited until the banner arrived, but then replaced the banner plus newline. To fix, wait for banner and newline. - For interactive psql replacing $banner\n is not sufficient, interactive psql outputs \r\n. - For interactive psql, where commands are echoed to stdout, the \echo command, rather than its output, would be matched. This would sometimes lead to output from the prior query, or wait_connect(), being returned in the next command. This also affected wait_connect(), leading to sometimes sending queries to psql before the connection actually was established. While debugging these issues I also found that it's hard to know whether a query separation banner was attributed to the right query. Make that easier by counting the queries each BackgroundPsql instance has emitted and include the number in the banner. Also emit psql stdout/stderr in query() and wait_connect() as Test::More notes, without that it's rather hard to debug some issues in CI and buildfarm. As this can cause issues not just to-be-added tests, but also existing ones, backpatch the fix to all supported versions. Reviewed-by: Daniel Gustafsson <daniel@yesql.se> Reviewed-by: Noah Misch <noah@leadboat.com> Discussion: https://postgr.es/m/wmovm6xcbwh7twdtymxuboaoarbvwj2haasd3sikzlb3dkgz76@n45rzycluzft Backpatch-through: 13
1 parent 6af51bf commit bb2bf22

File tree

1 file changed

+60
-15
lines changed

1 file changed

+60
-15
lines changed

src/test/perl/PostgreSQL/Test/BackgroundPsql.pm

Lines changed: 60 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ sub new
8989
'stdin' => '',
9090
'stdout' => '',
9191
'stderr' => '',
92-
'query_timer_restart' => undef
92+
'query_timer_restart' => undef,
93+
'query_cnt' => 1,
9394
};
9495
my $run;
9596

@@ -148,11 +149,25 @@ sub wait_connect
148149
# connection failures are caught here, relieving callers of the need to
149150
# handle those. (Right now, we have no particularly good handling for
150151
# errors anyway, but that might be added later.)
152+
#
153+
# See query() for details about why/how the banner is used.
151154
my $banner = "background_psql: ready";
152-
$self->{stdin} .= "\\echo $banner\n";
155+
my $banner_match = qr/(^|\n)$banner\r?\n/;
156+
$self->{stdin} .= "\\echo $banner\n\\warn $banner\n";
153157
$self->{run}->pump()
154-
until $self->{stdout} =~ /$banner/ || $self->{timeout}->is_expired;
155-
$self->{stdout} = ''; # clear out banner
158+
until ($self->{stdout} =~ /$banner_match/
159+
&& $self->{stderr} =~ /$banner\r?\n/)
160+
|| $self->{timeout}->is_expired;
161+
162+
note "connect output:\n",
163+
explain {
164+
stdout => $self->{stdout},
165+
stderr => $self->{stderr},
166+
};
167+
168+
# clear out banners
169+
$self->{stdout} = '';
170+
$self->{stderr} = '';
156171

157172
die "psql startup timed out" if $self->{timeout}->is_expired;
158173
}
@@ -219,27 +234,57 @@ sub query
219234
my ($self, $query) = @_;
220235
my $ret;
221236
my $output;
237+
my $query_cnt = $self->{query_cnt}++;
238+
222239
local $Test::Builder::Level = $Test::Builder::Level + 1;
223240

224-
note "issuing query via background psql: $query";
241+
note "issuing query $query_cnt via background psql: $query";
225242

226243
$self->{timeout}->start() if (defined($self->{query_timer_restart}));
227244

228245
# Feed the query to psql's stdin, followed by \n (so psql processes the
229246
# line), by a ; (so that psql issues the query, if it doesn't include a ;
230-
# itself), and a separator echoed with \echo, that we can wait on.
231-
my $banner = "background_psql: QUERY_SEPARATOR";
232-
$self->{stdin} .= "$query\n;\n\\echo $banner\n";
233-
234-
pump_until($self->{run}, $self->{timeout}, \$self->{stdout}, qr/$banner/);
247+
# itself), and a separator echoed both with \echo and \warn, that we can
248+
# wait on.
249+
#
250+
# To avoid somehow confusing the separator from separately issued queries,
251+
# and to make it easier to debug, we include a per-psql query counter in
252+
# the separator.
253+
#
254+
# We need both \echo (printing to stdout) and \warn (printing to stderr),
255+
# because on windows we can get data on stdout before seeing data on
256+
# stderr (or vice versa), even if psql printed them in the opposite
257+
# order. We therefore wait on both.
258+
#
259+
# We need to match for the newline, because we try to remove it below, and
260+
# it's possible to consume just the input *without* the newline. In
261+
# interactive psql we emit \r\n, so we need to allow for that. Also need
262+
# to be careful that we don't e.g. match the echoed \echo command, rather
263+
# than its output.
264+
my $banner = "background_psql: QUERY_SEPARATOR $query_cnt:";
265+
my $banner_match = qr/(^|\n)$banner\r?\n/;
266+
$self->{stdin} .= "$query\n;\n\\echo $banner\n\\warn $banner\n";
267+
pump_until(
268+
$self->{run}, $self->{timeout},
269+
\$self->{stdout}, qr/$banner_match/);
270+
pump_until(
271+
$self->{run}, $self->{timeout},
272+
\$self->{stderr}, qr/$banner_match/);
235273

236274
die "psql query timed out" if $self->{timeout}->is_expired;
237-
$output = $self->{stdout};
238275

239-
# Remove banner again, our caller doesn't care. The first newline is
240-
# optional, as there would not be one if consuming an empty query
241-
# result.
242-
$output =~ s/\n?$banner\n$//s;
276+
note "results query $query_cnt:\n",
277+
explain {
278+
stdout => $self->{stdout},
279+
stderr => $self->{stderr},
280+
};
281+
282+
# Remove banner from stdout and stderr, our caller doesn't care. The
283+
# first newline is optional, as there would not be one if consuming an
284+
# empty query result.
285+
$output = $self->{stdout};
286+
$output =~ s/$banner_match//;
287+
$self->{stderr} =~ s/$banner_match//;
243288

244289
# clear out output for the next query
245290
$self->{stdout} = '';

0 commit comments

Comments
 (0)