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 )