API-Docker

 view release on metacpan or  search on metacpan

t/stream_incremental.t  view on Meta::CPAN

    . 'with all of it, delivered when the daemon closed';
  is_deeply $handler->{summary}->(), { delivered => 3, stopped => 0 },
    'and the summary counts three units rather than one';
};

subtest 'the header block and the first bytes of the body in one arrival'
  => sub {
  # The case that made this a whole-transport change rather than a local one.
  # The head used to be read with <$sock>, which reads ahead: the bytes past
  # the blank line were already inside PerlIO's buffer, where nothing could
  # get at them. A body reader switched to sysread on its own would have
  # dropped exactly this.
  my @got;
  my $fh = burst_handle($HEAD . 'body starts here', ' and continues');
  my $handler = $client->_stream_handler('GET /v1.41/probe', 'on_chunk',
    sub { push @got, $_[0] }, 1);

  $client->_read_streaming_response($fh, 'GET', $handler, {});

  is_deeply \@got, ['body starts here', ' and continues'],
    'the read-ahead past the headers is the start of the body, and it is '
    . 'neither dropped nor delivered twice';
};

subtest 'a frame split across two arrivals is delivered once, whole' => sub {
  # The carry buffer's job, asserted here because karr k60 changes how often
  # it is asked to do it: it now sees the split the daemon actually made
  # rather than 64K blocks.
  my @got;
  my $whole = frame('hello world');
  my $fh = burst_handle($HEAD, substr($whole, 0, 5), substr($whole, 5));
  my $handler = $client->_stream_handler('GET /v1.41/probe', 'on_frame',
    sub { push @got, $_[0] }, 1);

  $client->_read_streaming_response($fh, 'GET', $handler, {});

  is_deeply \@got, [ { stream => 'stdout', data => 'hello world' } ],
    'the 8-byte header was split too, and the frame still came out once';
};

subtest 'a chunked stream still delivers per read, not per chunk' => sub {
  # The chunked path was never the broken one -- it asks for exactly the chunk
  # size, which is exactly what is there -- but it must not have regressed:
  # the engine is free to send an hour of log output as one chunk, so waiting
  # for a whole chunk would be the same bug in a different place.
  my @got;
  my $head = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n";
  my $fh = burst_handle($head . "12\r\nfirst ", 'second ', "third\r\n",
    "0\r\n\r\n");
  my $handler = $client->_stream_handler('GET /v1.41/probe', 'on_chunk',
    sub { push @got, $_[0] }, 1);

  $client->_read_streaming_response($fh, 'GET', $handler, {});

  is join('', @got), 'first second third', 'the whole chunk arrived';
  cmp_ok scalar @got, '>=', 3,
    'in at least as many calls as it arrived in (' . scalar(@got) . ')';
};

subtest 'one byte per read: every structure boundary is fragmented' => sub {
  # The cheapest way to say "no reader assumes anything arrives whole". At one
  # byte per read the status line, each header line, the blank line, the chunk
  # header, the chunk data, the CRLF after it and the terminating zero chunk
  # are each split across several reads -- and the 8-byte frame header inside
  # the payload is split eight ways.
  my $wire = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n";
  my $body = frame('one') . frame('two');
  $wire .= sprintf("%x\r\n", length $body) . $body . "\r\n0\r\n\r\n";

  my @got;
  my $fh = burst_handle(split //, $wire);
  my $handler = $client->_stream_handler('GET /v1.41/probe', 'on_frame',
    sub { push @got, $_[0] }, 1);

  $client->_read_streaming_response($fh, 'GET', $handler, {});

  is_deeply \@got,
    [ { stream => 'stdout', data => 'one' },
      { stream => 'stdout', data => 'two' } ],
    'both frames come out whole';

  # And the same wire read whole, so the buffered path is held to it too.
  my $buffered = $client->_read_response(burst_handle(split //, $wire), 'GET');
  is $buffered->[3], $body, 'the buffered reader reassembles it as well';
  is $buffered->[0], 200, 'and got the status line out of one-byte reads too';
};

subtest 'the caller can stop mid-stream with bytes still buffered' => sub {
  my @got;
  my $fh = burst_handle($HEAD, 'first', 'second', 'third');
  my $handler = $client->_stream_handler('GET /v1.41/probe', 'on_chunk',
    sub { push @got, $_[0]; $_[1]->() if @got == 2 }, 1);

  $client->_read_streaming_response($fh, 'GET', $handler, {});

  is_deeply \@got, ['first', 'second'], 'it stopped where it said to';
  is_deeply $handler->{summary}->(), { delivered => 2, stopped => 1 },
    'and said so';
};

# ---------------------------------------------------------------------------
# The real socket. A tied handle proves the loop; only a writer on the other
# end of a real socket proves that a read comes back with what has arrived
# rather than waiting for the rest.
#
# Runs the same scenario -- a peer that answers, writes three framed messages
# 0.15s apart and then closes -- and reports what the callback saw and when.
sub paced_writer {
  my ($option, $cb) = @_;

  socketpair(my $ours, my $theirs, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
    or return;
  my $pid = fork();
  return unless defined $pid;

  unless ($pid) {
    # The daemon side. The pauses are what a coalescing reader swallows.
    close $ours;
    syswrite $theirs, $HEAD;
    for my $text ('one', 'two', 'three') {
      sleep 0.15;



( run in 1.259 second using v1.01-cache-2.11-cpan-a49fcb8fa48 )