API-Docker

 view release on metacpan or  search on metacpan

t/read_timeout.t  view on Meta::CPAN

# returns 0, while an expiry sets EAGAIN and returns undef.
package Test::ReadTimeout::Handle;

sub TIEHANDLE {
  my ($class, $script) = @_;
  return bless { script => $script, i => 0 }, $class;
}

sub _next {
  my ($self) = @_;
  return $self->{script}[ $self->{i}++ ] || {};
}

# sysread reaches a tied handle through READ, so this is the whole interface
# the transport uses now -- READLINE is gone from here because nothing calls
# it any more: _read_line finds its terminator in the transport's own buffer.
sub READ {
  my $self = $_[0];
  my $act  = $self->_next;

  # sysread cannot deliver and expire in the same call, unlike the PerlIO
  # read() this harness was first written against. An entry that tries to be
  # both is a script that was not translated when karr k60 landed, and saying
  # so here is cheaper than the silent near-miss it would otherwise be.
  die "a scripted read cannot both deliver and expire\n"
    if ($act->{timeout} || $act->{eintr} || $act->{fail})
      && (defined $act->{line} || defined $act->{bytes});

  # Set last and read first: errno is only meaningful straight after the
  # operation that failed, which is exactly the discipline the code under
  # test has to keep. With keep_errno it is left exactly as the caller left
  # it, which is how a stale value gets in front of the check.
  unless ($act->{keep_errno}) {
    $! = $act->{timeout} ? Errno::EAGAIN()
      : $act->{eintr}    ? Errno::EINTR()
      : 0;
  }
  return undef if $act->{timeout} || $act->{eintr} || $act->{fail};

  my $data = defined $act->{line} ? $act->{line}
    : defined $act->{bytes} ? $act->{bytes} : '';
  $_[1] = $data;
  # 0 is the clean end of the response, and is the only thing that means it.
  return length $data;
}

sub CLOSE { 1 }

package main;

my $client = API::Docker->new(
  host        => 'unix:///nonexistent.sock',
  api_version => '1.41',
);

my $ENDPOINT = 'GET /v1.41/probe';

sub scripted {
  my (@script) = @_;
  no warnings "once";
  my $glob = \do { local *HANDLE };
  tie *$glob, 'Test::ReadTimeout::Handle', \@script;
  return $glob;
}

# A context with the clock running, and one without. The second is what every
# call made before this option existed passes, and it must leave every read
# site behaving exactly as it did.
sub ctx      { return { endpoint => $ENDPOINT, timeout => 2 } }
sub ctx_off  { return { endpoint => $ENDPOINT } }

# Run $code and report whether it raised a timeout, so the two meanings of one
# short read can be asserted side by side.
sub timed_out {
  my ($code) = @_;
  my @out = eval { $code->() };
  my $err = $@;
  return (undef, $err) if $err && ref $err
    && $err->isa('API::Docker::Error::Timeout');
  return (\@out, undef) unless $err;
  return (undef, undef, $err);
}

# Reading an attribute off whatever was raised, so a mutation that stops
# raising one fails the assertion it belongs to instead of dying and taking
# the rest of the file with it -- a red test has to stay readable.
sub attr {
  my ($err, $name) = @_;
  return ref $err && $err->can($name) ? $err->$name : undef;
}

# The pair of assertions every read site gets: with EAGAIN it croaks with a
# timeout, without it it does whatever a real end of the response means at
# that site. A test that only made the first half would pass just as well
# against a transport that croaked on every end of response.
#
# The second half is declared, not merely inspected. Each site says which of
# the two shapes it has -- `returns => sub {...}` for a site where the close
# really is the end of the response, `raises => sub {...}` for one where it is
# not -- and site_ok asserts that it is that shape and not the other one. The
# earlier version took a single check sub and ran it either way, so a site
# that started croaking where it used to return still passed as long as the
# check itself did not look at the return value. Four of the streaming sites
# below were in exactly that position when karr k64 landed: the check asserted
# what had reached the callback, which a croak does not change, so the change
# in outcome went unseen. Declaring the shape is what makes those four able to
# fail.
sub site_ok {
  my ($name, $make_handle, $drive, %eof) = @_;

  die "site_ok $name: declare exactly one of returns/raises\n"
    unless 1 == grep { $eof{$_} } qw( returns raises );

  subtest $name => sub {
    my ($out, $err, $other) = timed_out(
      sub { $drive->($make_handle->(1), ctx()) });
    ok !$other, 'the expiry raised nothing but a timeout'
      or diag "raised instead: $other";
    ok $err, 'ran out of time -> API::Docker::Error::Timeout';
    is $err && attr($err, "timeout"), 2, 'the error names the timeout that expired'
      if $err;
    is $err && attr($err, "endpoint"), $ENDPOINT, 'and the request it belongs to'
      if $err;



( run in 0.576 second using v1.01-cache-2.11-cpan-4716db65a44 )