API-Docker
view release on metacpan or search on metacpan
t/read_timeout.t view on Meta::CPAN
'and on the raw-stream path, which is the one karr k52 hangs on and '
. 'where it matters most -- and where the two bursts are two calls '
. 'rather than one 64K read, which is karr k60';
};
}
# ---------------------------------------------------------------------------
# What the exception carries
# ---------------------------------------------------------------------------
subtest 'the bytes that did arrive come out with the exception' => sub {
subtest 'a content-length body' => sub {
my $fh = scripted({ bytes => 'hello ' }, { bytes => 'wor' },
{ timeout => 1 });
eval { $client->_read_body($fh, { 'content-length' => 11 }, 'GET', ctx()) };
my $err = $@;
isa_ok $err, 'API::Docker::Error::Timeout';
is attr($err, "partial"), 'hello wor',
'everything read so far, the bytes of the read that expired included';
is attr($err, "summary"), undef, 'no summary: nothing was streamed';
};
subtest 'a chunked body keeps the chunk it stalled inside' => sub {
my $fh = scripted({ line => "5\r\n" }, { bytes => 'hello' },
{ line => "\r\n" }, { line => "6\r\n" }, { bytes => ' wor' },
{ timeout => 1 });
eval { $client->_read_chunked($fh, ctx()) };
my $err = $@;
isa_ok $err, 'API::Docker::Error::Timeout';
is attr($err, "partial"), 'hello wor',
'the completed chunk and the part of the one still arriving';
};
subtest 'a close-delimited body' => sub {
my $fh = scripted({ bytes => 'partial frames' }, { timeout => 1 });
eval { $client->_read_body($fh, {}, 'GET', ctx()) };
isa_ok $@, 'API::Docker::Error::Timeout';
is attr($@, "partial"), 'partial frames', 'the bytes the slurp had collected';
};
subtest 'a streamed request carries the summary instead' => sub {
my @got;
my $fh = scripted($HEAD_OK, { line => "Transfer-Encoding: chunked\r\n" },
$BLANK,
{ line => "5\r\n" }, { bytes => 'hello' }, { line => "\r\n" },
{ line => "5\r\n" }, { bytes => 'there' }, { line => "\r\n" },
{ timeout => 1 });
eval {
$client->_read_streaming_response($fh, 'GET', chunk_handler(\@got), ctx());
};
my $err = $@;
isa_ok $err, 'API::Docker::Error::Timeout';
is_deeply attr($err, "summary"), { delivered => 2, stopped => 0 },
'the units the callback did get, counted the way a clean end counts them';
is attr($err, "partial"), '',
'and no body: a streamed request keeps none by design';
like attr($err, "message"), qr/2 units/, 'the message says so too';
};
subtest 'everything that arrived is delivered before the expiry' => sub {
# This is the shape karr k52 actually has: everything the daemon had to
# say arrives, and the socket then stays open and silent. Measured against
# Podman 5.8.4 on an attach to an exited container: two frames, 42 bytes,
# delivered 0 before karr k59 and 2 after.
#
# Under read() those 42 bytes and the expiry were one call, and k59 had to
# rescue them out of it. Under sysread they are two -- the delivery, then
# the silence -- and the property holds without a rescue, which is why the
# script below has two entries where it used to have one. What is asserted
# is the property, not the mechanism: at the moment the exception is
# raised, the caller is holding everything the daemon sent.
my @got;
my $fh = scripted($HEAD_OK, $BLANK,
{ bytes => 'frame one and two' }, { timeout => 1 });
eval {
$client->_read_streaming_response($fh, 'GET', chunk_handler(\@got), ctx());
};
my $err = $@;
isa_ok $err, 'API::Docker::Error::Timeout';
is_deeply \@got, ['frame one and two'],
'the callback got every byte that arrived';
is_deeply attr($err, 'summary'), { delivered => 1, stopped => 0 },
'and the summary counts them, so it says what happened rather than '
. 'undercounting it';
};
subtest 'an error body on the way to a >= 400 croak is counted in bytes' => sub {
# The summary is only armed past the status line, so a timeout while
# reading the short JSON body of a failure is not reported as units that
# nothing delivered.
my @got;
my $fh = scripted({ line => "HTTP/1.1 500 Internal Server Error\r\n" },
{ line => "Content-Length: 40\r\n" }, $BLANK,
{ bytes => '{"message":"bo' }, { timeout => 1 });
eval {
$client->_read_streaming_response($fh, 'GET', chunk_handler(\@got), ctx());
};
my $err = $@;
isa_ok $err, 'API::Docker::Error::Timeout';
is attr($err, "summary"), undef, 'no summary for a body that was never a stream';
is attr($err, "partial"), '{"message":"bo', 'the bytes of the error body';
};
subtest 'nothing at all is said so, not reported as zero bytes' => sub {
my $fh = scripted($HEAD_OK, { line => "Content-Length: 11\r\n" }, $BLANK,
{ timeout => 1 });
eval { $client->_read_response($fh, 'GET', ctx()) };
my $err = $@;
isa_ok $err, 'API::Docker::Error::Timeout';
is attr($err, "partial"), '', 'partial is empty';
like attr($err, "message"), qr/nothing arrived at all/, 'and the message says so';
};
};
subtest 'the exception is still the string it replaces' => sub {
my $fh = scripted({ timeout => 1 });
eval { $client->_read_head($fh, ctx()) };
my $err = $@;
isa_ok $err, 'API::Docker::Error::Timeout';
ok overload::Overloaded($err),
'stringification is in place -- namespace::clean before use overload';
unlike "$err", qr/=HASH\(0x/,
( run in 1.106 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )