#!/usr/bin/perl -w
# vim: ts=2 sw=2 filetype=perl expandtab
use strict;
use Test::More tests => 17;
use POE::Pipe::OneWay;
BEGIN { use_ok("POE::Driver::SysRW") }
# Start with some errors.
eval { my $d = POE::Driver::SysRW->new( BlockSize => 0 ) };
ok(
$@ && $@ =~ /BlockSize must be greater than 0/,
"disallow zero or negative block sizes"
);
eval { my $d = POE::Driver::SysRW->new( 0 ) };
ok(
$@ && $@ =~ /requires an even number of parameters/,
"disallow zero or negative block sizes"
);
eval { my $d = POE::Driver::SysRW->new( Booga => 1 ) };
ok(
$@ && $@ =~ /unknown parameter.*Booga/,
"disallow unknown parameters"
);
# This block of tests also exercises the driver with its default
# constructor parameters.
{ my $d = POE::Driver::SysRW->new();
use Symbol qw(gensym);
my $fh = gensym();
open $fh, ">deleteme.now" or die $!;
$! = 0;
open(SAVE_STDERR, ">&STDERR") or die $!;
close(STDERR) or die $!;
my $get_ret = $d->get($fh);
ok(!defined($get_ret), "get() returns undef on error");
ok($!, "get() sets \$! on error ($!)");
open(STDERR, ">&SAVE_STDERR") or die $!;
close(SAVE_STDERR) or die $!;
close $fh;
unlink "deleteme.now";
}
my $d = POE::Driver::SysRW->new( BlockSize => 1024 );
# Empty put().
{ my $octets_left = $d->put([ ]);
ok( $octets_left == 0, "buffered 0 octets on empty put()" );
}
ok( $d->get_out_messages_buffered() == 0, "no messages buffered" );
# The number of octets we expect in the driver's put() buffer.
my $expected = 0;
# Put() returns the correct number of octets.
{ my $string_to_put = "test" x 10;
my $length_to_put = length($string_to_put);
$expected += $length_to_put;
my $octets_left = $d->put([ $string_to_put ]);
ok(
$octets_left == $expected,
"first put: buffer contains $octets_left octets (should be $expected)"
);
}
# Only one message buffered.
ok( $d->get_out_messages_buffered() == 1, "one message buffered" );
# Put() returns the correct number of octets on a subsequent call.
{ my $string_to_put = "more test" x 5;
my $length_to_put = length($string_to_put);
$expected += $length_to_put;
my $octets_left = $d->put([ $string_to_put ]);
ok(
$octets_left == $expected,
"second put: buffer contains $octets_left octets (should be $expected)"
);
}
# Remaining tests require some live handles.
my ($r, $w) = POE::Pipe::OneWay->new();
die "can't open a pipe: $!" unless $r;
nonblocking($w);
nonblocking($r);
# Number of flushed octets == number of read octets.
{ my ($flushed_count, $full) = write_until_pipe_is_full($d, $w);
my ($read_count) = read_until_pipe_is_empty($d, $r);
ok(
$flushed_count == $read_count,
"flushed $flushed_count octets == read $read_count octets"
);
}
# Flush the buffer and the pipe.
while (flush_remaining_buffer($d, $w)) {
read_until_pipe_is_empty($d, $r);
}
{
my $out_messages = $d->get_out_messages_buffered();
ok($out_messages == 0, "buffer exhausted (got $out_messages wanted 0)");
}
# Get() returns undef ($! == 0) on EOF.
{ write_until_pipe_is_full($d, $w);
close($w);
open(SAVE_STDERR, ">&STDERR") or die $!;
close(STDERR) or die $!;
while (1) {
$! = 1;
last unless defined $d->get($r);
}
pass("driver returns undef on eof");
ok($! == 0, "\$! is clear on eof");
open(STDERR, ">&SAVE_STDERR") or die $!;
close(SAVE_STDERR) or die $!;
}
# Flush() returns the number of octets remaining, and sets $! to
# nonzero on major error.
{ open(SAVE_STDERR, ">&STDERR") or die $!;
close(STDERR) or die $!;
# Make sure $w is closed. Sometimes, like on Cygwin, it isn't.
close $w;
$! = 0;
my $error_left = $d->flush($w);
ok($error_left, "put() returns octets left on error");
ok($!, "put() sets \$! nonzero on error");
open(STDERR, ">&SAVE_STDERR") or die $!;
close(SAVE_STDERR) or die $!;
}
exit 0;
# Buffer data, and flush it, until the pipe refuses to hold more data.
# This should also cause the driver to experience an EAGAIN or
# EWOULDBLOCK on write.
sub write_until_pipe_is_full {
my ($driver, $handle) = @_;
# Hopefully bigger than any system buffer ever.
my $big_chunk = "*" x (1024 * 1024);
my $flushed = 0;
my $full = 0;
while (1) {
# Put a big chunk into the buffer.
my $buffered = $driver->put([ $big_chunk ]);
# Try to flush it.
my $after_flush = $driver->flush($handle);
# How much was flushed?
$flushed += $buffered - $after_flush;
# If there's data left, then this flush failed.
last if $after_flush;
}
if (wantarray) {
return ($flushed, $full);
}
return $flushed;
}
# Assume the driven has buffered data. This makes sure it's flushed,
# or at least the pipe is clogged. Combine it with
# read_until_pipe_is_empty() to flush the driver and the pipe.
sub flush_remaining_buffer {
my ($driver, $handle) = @_;
my $before_flush = $driver->get_out_messages_buffered();
$driver->flush($handle);
return $before_flush;
}
# Read until there's nothing left to read from the pipe. This should
# exercise the driver's EAGAIN/EWOULDBLOCK code on the read side.
sub read_until_pipe_is_empty {
my ($driver, $handle) = @_;
my $read_octets = 0;
# SunOS catalogue1 5.11 snv_101b i86pc i386 i86pc
# Sometimes returns "empty" when there's data in the pipe.
# Looping again seems to fetch the remaining data, though.
for (1..3) {
while (1) {
my $data = $driver->get($handle);
last unless defined($data) and @$data;
$read_octets += length() foreach @$data;
}
}
return $read_octets;
}
# Portable nonblocking sub. blocking(0) doesn't do it all the time,
# everywhere, and it sucks.
#
# This sub sucks, too. The code is lifted almost verbatim from
# POE::Resource::FileHandles. That code should probably be made a
# library function, but where should it go?
sub nonblocking {
my $handle = shift;
# For DOSISH systems like OS/2. Wrapped in eval{} in case it's a
# tied handle that doesn't support binmode.
eval { binmode *$handle };
# Turn off blocking.
eval { $handle->blocking(0); $handle->blocking(); };
# Turn off buffering.
CORE::select((CORE::select($handle), $| = 1)[0]);
}