File Coverage

blib/lib/Test/Future/IO/Impl.pm
Criterion Covered Total %
statement 29 305 9.5
branch 0 96 0.0
condition 0 7 0.0
subroutine 10 34 29.4
pod 1 15 6.6
total 40 457 8.7


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2021-2026 -- leonerd@leonerd.org.uk
5              
6             package Test::Future::IO::Impl 0.21;
7              
8 1     1   374051 use v5.14;
  1         4  
9 1     1   6 use warnings;
  1         2  
  1         60  
10              
11 1     1   9 use Test2::V0;
  1         2  
  1         9  
12 1     1   1587 use Test2::API ();
  1         3  
  1         30  
13              
14 1     1   5 use Errno qw( EAGAIN EINVAL EPIPE );
  1         2  
  1         230  
15 1     1   644 use IO::Handle;
  1         7752  
  1         67  
16 1     1   554 use IO::Poll qw( POLLIN POLLOUT POLLHUP POLLERR );
  1         1056  
  1         103  
17 1         282 use Socket qw(
18             pack_sockaddr_in sockaddr_family INADDR_LOOPBACK
19             AF_INET AF_UNIX SOCK_DGRAM SOCK_STREAM PF_UNSPEC
20 1     1   730 );
  1         5045  
21 1     1   10 use Time::HiRes qw( sleep time );
  1         2  
  1         10  
22              
23 1     1   82 use Exporter 'import';
  1         2  
  1         5306  
24             our @EXPORT = qw( run_tests );
25              
26             =head1 NAME
27              
28             C - acceptance tests for C implementations
29              
30             =head1 SYNOPSIS
31              
32             =for highlighter language=perl
33              
34             use Test::More;
35             use Test::Future::IO::Impl;
36              
37             use Future::IO;
38             use Future::IO::Impl::MyNewImpl;
39              
40             run_tests 'sleep';
41              
42             done_testing;
43              
44             =head1 DESCRIPTION
45              
46             This module contains a collection of acceptance tests for implementations of
47             L.
48              
49             =cut
50              
51             =head1 FUNCTIONS
52              
53             =cut
54              
55             my $errstr_EPIPE = do {
56             # On MSWin32 we don't get EPIPE, but EINVAL
57             local $! = $^O eq "MSWin32" ? EINVAL : EPIPE; "$!";
58             };
59              
60             my $errstr_ECONNREFUSED = do {
61             local $! = Errno::ECONNREFUSED; "$!";
62             };
63              
64             sub time_about(&@)
65             {
66 0     0 0   my ( $code, $want_time, $name ) = @_;
67 0           my $ctx = Test2::API::context;
68              
69 0           my $t0 = time();
70 0           $code->();
71 0           my $t1 = time();
72              
73 0           my $got_time = $t1 - $t0;
74 0 0 0       $ctx->ok(
75             $got_time >= $want_time * 0.9 && $got_time <= $want_time * 1.5, $name
76             ) or
77             $ctx->diag( sprintf "Test took %.3f seconds", $got_time );
78              
79 0           $ctx->release;
80             }
81              
82             =head2 run_tests
83              
84             run_tests @suitenames;
85              
86             Runs a collection of tests against C. It is expected that the
87             caller has already loaded the specific implementation module to be tested
88             against before this function is called.
89              
90             =cut
91              
92             sub run_tests
93             {
94 0     0 1   foreach my $test ( @_ ) {
95 0 0         my $code = __PACKAGE__->can( "run_${test}_test" )
96             or die "Unrecognised test suite name $test";
97 0           __PACKAGE__->$code();
98             }
99             }
100              
101             =head1 TEST SUITES
102              
103             The following test suite names may be passed to the L function:
104              
105             =cut
106              
107             =head2 accept
108              
109             Tests the C<< Future::IO->accept >> method.
110              
111             =cut
112              
113             sub run_accept_test
114             {
115 0     0 0   require IO::Socket::INET;
116              
117 0 0         my $serversock = IO::Socket::INET->new(
118             Type => Socket::SOCK_STREAM(),
119             LocalAddr => "localhost",
120             LocalPort => 0,
121             Listen => 1,
122             ) or die "Cannot socket()/listen() - $@";
123              
124 0           $serversock->blocking( 0 );
125              
126 0           my $f = Future::IO->accept( $serversock );
127              
128             # Some platforms have assigned 127.0.0.1 here; others have left 0.0.0.0
129             # If it's still 0.0.0.0, then guess that maybe connecting to 127.0.0.1 will
130             # work
131 0 0         my $sockname = ( $serversock->sockhost ne "0.0.0.0" )
132             ? $serversock->sockname
133             : pack_sockaddr_in( $serversock->sockport, INADDR_LOOPBACK );
134              
135 0 0         my $clientsock = IO::Socket::INET->new(
136             Type => Socket::SOCK_STREAM(),
137             ) or die "Cannot socket() - $@";
138 0 0         $clientsock->connect( $sockname ) or die "Cannot connect() - $@";
139              
140 0           my $acceptedsock = $f->get;
141              
142 0           ok( $clientsock->peername eq $acceptedsock->sockname, 'Accepted socket address matches' );
143             }
144              
145             =head2 connect
146              
147             Tests the C<< Future::IO->connect >> method.
148              
149             =cut
150              
151             sub run_connect_test
152             {
153 0     0 0   require IO::Socket::INET;
154              
155 0 0         my $serversock = IO::Socket::INET->new(
156             Type => Socket::SOCK_STREAM(),
157             LocalAddr => "localhost",
158             LocalPort => 0,
159             Listen => 1,
160             ) or die "Cannot socket()/listen() - $@";
161              
162             # Some platforms have assigned 127.0.0.1 here; others have left 0.0.0.0
163             # If it's still 0.0.0.0, then guess that maybe connecting to 127.0.0.1 will
164             # work
165 0 0         my $sockname = ( $serversock->sockhost ne "0.0.0.0" )
166             ? $serversock->sockname
167             : pack_sockaddr_in( $serversock->sockport, INADDR_LOOPBACK );
168              
169             # ->connect success
170             {
171 0 0         my $clientsock = IO::Socket::INET->new(
  0            
172             Type => Socket::SOCK_STREAM(),
173             ) or die "Cannot socket() - $@";
174 0           $clientsock->blocking( 0 );
175              
176 0           my $f = Future::IO->connect( $clientsock, $sockname );
177              
178 0           $f->get;
179              
180 0           my $acceptedsock = $serversock->accept;
181 0           ok( $clientsock->peername eq $acceptedsock->sockname, 'Accepted socket address matches' );
182             }
183              
184 0           $serversock->close;
185 0           undef $serversock;
186              
187             # I really hate this, but apparently tests on most OSes will fail if we
188             # don't do this. Technically Linux can get away without it but it's only
189             # 100msec, nobody will notice
190 0           sleep 0.1;
191 0 0         sleep 1 if $^O eq "MSWin32"; # Windows needs to wait longer
192              
193             # Sometimes a connect() doesn't fail, because of weird setups. Windows
194             # often doesn't fail here. Maybe weird networking. I really don't know and
195             # have no way to find out. Rather than make the tests complain here, we'll
196             # just assert that Future::IO->connect fails *if* a regular blocking
197             # connect fails first.
198 0 0         my $probe_clientsock = IO::Socket::INET->new(
199             Type => Socket::SOCK_STREAM(),
200             ) or die "Cannot socket() - $@";
201 0           my $connect_fails = !defined $probe_clientsock->connect( $sockname );
202              
203             # ->connect fails
204 0 0         if( $connect_fails ) {
205 0 0         my $clientsock = IO::Socket::INET->new(
206             Type => Socket::SOCK_STREAM(),
207             ) or die "Cannot socket() - $@";
208 0           $clientsock->blocking( 0 );
209              
210 0           my $f = Future::IO->connect( $clientsock, $sockname );
211              
212 0           ok( !eval { $f->get; 1 }, 'Future::IO->connect fails on closed server' );
  0            
  0            
213              
214 0           is( [ $f->failure ],
215             [ "connect: $errstr_ECONNREFUSED\n", connect => $clientsock, $errstr_ECONNREFUSED ],
216             'Future::IO->connect failure' );
217             }
218             }
219              
220             =head2 poll
221              
222             I
223              
224             Tests the C<< Future::IO->poll >> method.
225              
226             =cut
227              
228             # because the Future::IO default impl cannot handle HUP
229             sub run_poll_no_hup_test
230             {
231             # POLLIN
232             {
233 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
234              
235 0           $wr->autoflush();
236 0           $wr->print( "BYTES" );
237              
238 0           my $f = Future::IO->poll( $rd, POLLIN );
239              
240 0           is( scalar $f->get, POLLIN, "Future::IO->poll(POLLIN) yields POLLIN on readable filehandle" );
241              
242 0           my $f1 = Future::IO->poll( $rd, POLLIN );
243 0           my $f2 = Future::IO->poll( $rd, POLLIN );
244              
245 0           is( [ scalar $f1->get, scalar $f2->get ], [ POLLIN, POLLIN ],
246             'Future::IO->poll(POLLIN) can enqueue two POLLIN tests' );
247              
248 0           my $f4;
249             my $f3 = Future::IO->poll( $rd, POLLIN )
250 0     0     ->on_done( sub { $f4 = Future::IO->poll( $rd, POLLIN ); } );
  0            
251              
252 0           is( scalar $f3->get, POLLIN,
253             'Another ->poll(POLLIN) yields after' );
254 0           ok( defined $f4, 'Subsequent future enqueued' );
255 0           ok( !$f4->is_ready, 'Subsequent future is not yet ready after one IO round' );
256              
257 0           is( scalar $f4->get, POLLIN,
258             'Subsequent future yields after second round' );
259             }
260              
261             # POLLOUT
262             {
263 0 0   0 0   pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
264              
265 0           my $f = Future::IO->poll( $wr, POLLOUT );
266              
267 0           is( scalar $f->get, POLLOUT, "Future::IO->poll(POLLOUT) yields POLLOUT on writable filehandle" );
268              
269 0           my $f1 = Future::IO->poll( $wr, POLLOUT );
270 0           my $f2 = Future::IO->poll( $wr, POLLOUT );
271              
272 0           is( [ scalar $f1->get, scalar $f2->get ], [ POLLOUT, POLLOUT ],
273             'Future::IO->poll(POLLOUT) can enqueue two POLLOUT tests' );
274              
275 0           my $f4;
276             my $f3 = Future::IO->poll( $wr, POLLOUT )
277 0     0     ->on_done( sub { $f4 = Future::IO->poll( $wr, POLLOUT ); } );
  0            
278              
279 0           is( scalar $f3->get, POLLOUT,
280             'Another ->poll(POLLOUT) yields after' );
281 0           ok( defined $f4, 'Subsequent future enqueued' );
282 0           ok( !$f4->is_ready, 'Subsequent future is not yet ready after one IO round' );
283              
284 0           is( scalar $f4->get, POLLOUT,
285             'Subsequent future yields after second round' );
286             }
287              
288             # POLLIN+POLLOUT at once
289             {
290 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
291              
292 0           $wr->autoflush();
293 0           $wr->print( "BYTES" );
294              
295 0           my ( $frd, $fwr );
296              
297             # IN+OUT on reading end
298 0           $frd = Future::IO->poll( $rd, POLLIN );
299 0           $fwr = Future::IO->poll( $rd, POLLOUT );
300              
301 0           is( scalar $frd->get, POLLIN, "Future::IO->poll(POLLIN) yields POLLIN on readable with simultaneous POLLOUT" );
302             # Don't assert on what $fwr saw here, as OSes/impls might differ
303 0           $fwr->cancel;
304              
305             # IN+OUT on writing end
306 0           $frd = Future::IO->poll( $wr, POLLIN );
307 0           $fwr = Future::IO->poll( $wr, POLLOUT );
308              
309 0           is( scalar $fwr->get, POLLOUT, "Future::IO->poll(POLLOUT) yields POLLOUT on writable with simultaneous POLLIN" );
310             # Don't assert on what $frd saw here, as OSes/impls might differ
311 0           $frd->cancel;
312             }
313              
314             # POLLIN doesn't fire accidentally on POLLOUT-only handle
315             {
316 0           require Socket;
  0            
  0            
317 0           require IO::Socket::UNIX;
318              
319 0 0         my ( $s1, $s2 ) = IO::Socket::UNIX->socketpair( Socket::PF_UNIX, Socket::SOCK_STREAM, 0 )
320             or last; # some OSes e.g. Win32 cannot do PF_UNIX socketpairs
321              
322             # Try both orders;
323 0           foreach my $first (qw( IN OUT )) {
324 0           my ( $fin, $fout );
325 0 0         $fin = Future::IO->poll( $s1, POLLIN ) if $first eq "IN";
326 0           $fout = Future::IO->poll( $s1, POLLOUT );
327 0 0         $fin = Future::IO->poll( $s1, POLLIN ) if $first eq "OUT";
328              
329 0           is( scalar $fout->get, POLLOUT, "Future::IO->poll(POLLOUT) yields POLLOUT on writable $first first" );
330 0           ok( !$fin->is_ready, "Future::IO->poll(POLLIN) remains pending on writeable $first first" );
331             }
332             }
333             }
334              
335             sub run_poll_test
336             {
337 0     0 0   run_poll_no_hup_test();
338              
339             # POLLHUP
340             {
341             # closing the writing end of a pipe puts the reading end at hangup condition
342 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
343 0           close $wr;
344              
345 0           my $f = Future::IO->poll( $rd, POLLIN|POLLHUP );
346              
347 0           is( ( scalar $f->get ) & POLLHUP, POLLHUP,
348             "Future::IO->poll(POLLIN) yields at least POLLHUP on hangup-in filehandle" );
349             }
350              
351             # POLLERR
352             {
353             # closing the reading end of a pipe puts the writing end at error condition, because EPIPE
354 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
  0            
355 0           close $rd;
356              
357 0           my $f = Future::IO->poll( $wr, POLLOUT|POLLHUP );
358              
359             # We expect at least one of POLLERR or POLLHUP, we might also see POLLOUT
360             # well but lets not care about that
361 0           my $got_revents = $f->get;
362 0           ok( $got_revents & (POLLERR|POLLHUP),
363             "Future::IO->poll(POLLOUT) yields POLLERR or POLLHUP on hangup-out filehandle" );
364             }
365             }
366              
367             =head2 recv, recvfrom
368              
369             I
370              
371             Tests the C<< Future::IO->recv >> and C<< Future::IO->recvfrom >> methods.
372              
373             =cut
374              
375             # Getting a read/write socket pair which has working addresses is nontrivial.
376             # AF_UNIX sockets created by socketpair() literally have no addresses. AF_INET
377             # sockets would always have an address, but socketpair() can't create
378             # connected AF_INET pairs on most platforms. Grr.
379             # We'll make our own socketpair-alike that does.
380             sub _socketpair_INET_DGRAM
381             {
382 0     0     my ( $connected ) = @_;
383 0   0       $connected //= 1;
384              
385 0           require IO::Socket::INET;
386              
387             # The IO::Socket constructors are unhelpful to us here; we'll do it ourselves
388 0 0         my $rd = IO::Socket::INET->new
389             ->socket( AF_INET, SOCK_DGRAM, 0 ) or die "Cannot socket rd - $!";
390 0 0         $rd->bind( pack_sockaddr_in( 0, INADDR_LOOPBACK ) ) or die "Cannot bind rd - $!";
391              
392 0           my $wr = IO::Socket::INET->new
393             ->socket( AF_INET, SOCK_DGRAM, 0 );
394 0 0 0       $wr->connect( $rd->sockname ) or die "Cannot connect wr - $!"
395             if $connected;
396              
397 0           return ( $rd, $wr );
398             }
399              
400 0     0 0   sub run_recv_test { _run_recv_test( 'recv', 0 ); }
401 0     0 0   sub run_recvfrom_test { _run_recv_test( 'recvfrom', 1 ); }
402             sub _run_recv_test
403             {
404 0     0     my ( $method, $expect_fromaddr ) = @_;
405              
406             # yielding bytes
407             {
408 0           my ( $rd, $wr ) = _socketpair_INET_DGRAM();
  0            
409              
410 0           $wr->autoflush();
411 0           $wr->send( "BYTES" );
412              
413 0           my $f = Future::IO->$method( $rd, 5 );
414              
415 0           is( scalar $f->get, "BYTES", "Future::IO->$method yields bytes from socket" );
416             # We can't know exactly what address it will be but
417 0           my $fromaddr = ( $f->get )[1];
418 0 0         ok( defined $fromaddr, "Future::IO->$method also yields a fromaddr" )
419             if $expect_fromaddr;
420 0 0         is( sockaddr_family( $fromaddr ), AF_INET, "Future::IO->$method fromaddr is valid AF_INET address" )
421             if $expect_fromaddr;
422             }
423              
424             # From here onwards we don't need working sockaddr/peeraddr so we can just
425             # use simpler IO::Socket::UNIX->socketpair instead
426              
427 0 0         return if $^O eq "MSWin32";
428              
429 0           require IO::Socket::UNIX;
430              
431             # yielding EOF
432             {
433 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
434             or die "Cannot socketpair() - $!";
435 0           $wr->close; undef $wr;
  0            
436              
437 0           my $f = Future::IO->$method( $rd, 1 );
438              
439 0           is ( [ $f->get ], [], "Future::IO->$method yields nothing on EOF" );
440             }
441              
442             # can be cancelled
443             {
444 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
  0            
  0            
445             or die "Cannot socketpair() - $!";
446              
447 0           $wr->autoflush();
448 0           $wr->send( "BYTES" );
449              
450 0           my $f1 = Future::IO->$method( $rd, 3 );
451 0           my $f2 = Future::IO->$method( $rd, 3 );
452              
453 0           $f1->cancel;
454              
455             # At this point we don't know if $f1 performed its recv or not. There's
456             # two possible things we might see from $f2.
457              
458 0           like( scalar $f2->get, qr/^(?:BYT|ES)$/,
459             "Result of second Future::IO->$method after first is cancelled" );
460             }
461             }
462              
463             =head2 send
464              
465             I
466              
467             Tests the C<< Future::IO->send >> method.
468              
469             =cut
470              
471             sub run_send_test
472             {
473             # success
474             {
475             # An unconnected socketpair to prove that ->send used the correct address later on
476 0     0 0   my ( $rd, $wr ) = _socketpair_INET_DGRAM( 0 );
  0            
477              
478 0           my $f = Future::IO->send( $wr, "BYTES", 0, $rd->sockname );
479              
480 0           is( scalar $f->get, 5, 'Future::IO->send yields sent count' );
481              
482 0           $rd->recv( my $buf, 5 );
483 0           is( $buf, "BYTES", 'Future::IO->send sent bytes' );
484             }
485              
486             # From here onwards we don't need working sockaddr/peeraddr so we can just
487             # use simpler IO::Socket::UNIX->socketpair instead
488              
489 0 0         return if $^O eq "MSWin32";
490              
491 0           require IO::Socket::UNIX;
492              
493             # yielding EAGAIN
494             SKIP: {
495 0 0         $^O eq "MSWin32" and skip "MSWin32 doesn't do EAGAIN properly", 2;
496              
497 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
498             or die "Cannot socketpair() - $!";
499 0           $wr->blocking( 0 );
500              
501             # Attempt to fill the buffer
502 0           $wr->write( "X" x 4096 ) for 1..256;
503              
504 0           my $f = Future::IO->send( $wr, "more" );
505              
506 0           ok( !$f->is_ready, '$f is still pending' );
507              
508             # Now make some space. We need to drain it quite a lot for mechanisms
509             # like ppoll() to be happy that the socket is actually writable
510 0           $rd->blocking( 0 );
511 0           $rd->read( my $buf, 4096 ) for 1..256;
512              
513 0           is( scalar $f->get, 4, 'Future::IO->send yields written count' );
514             }
515              
516             # yielding EPIPE
517             {
518 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
  0            
519             or die "Cannot socketpair() - $!";
520 0           $rd->close; undef $rd;
  0            
521              
522 0           local $SIG{PIPE} = 'IGNORE';
523              
524 0           my $f = Future::IO->send( $wr, "BYTES" );
525              
526 0           $f->await;
527 0           ok( $f->is_ready, '->send future is now ready after EPIPE' );
528              
529             # Sometimes we get EPIPE out of a send(2) system call (e.g Linux).
530             # Sometimes we get a croak out of IO::Socket->send itself because it
531             # checked getpeername() and found it missing (e.g. most BSDs). We
532             # shouldn't be overly concerned with _what_ the failure is, only that
533             # it failed somehow.
534 0           ok( scalar $f->failure, 'Future::IO->send failed after peer closed' );
535             }
536              
537             # can be cancelled
538             {
539 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
  0            
  0            
540             or die "Cannot socketpair() - $!";
541              
542 0           my $f1 = Future::IO->send( $wr, "BY" );
543 0           my $f2 = Future::IO->send( $wr, "TES" );
544              
545 0           $f1->cancel;
546              
547 0           is( scalar $f2->get, 3, 'Future::IO->send after cancelled one still works' );
548              
549 0           $rd->read( my $buf, 3 );
550              
551             # At this point we don't know if $f1 performed its send or not. There's
552             # two possible things we might see from the buffer. Either way, the
553             # presence of a 'T' means that $f2 ran.
554              
555 0           like( $buf, qr/^(?:BYT|TES)$/,
556             "A second Future::IO->send takes place after first is cancelled" );
557             }
558             }
559              
560             =head2 sleep
561              
562             Tests the C<< Future::IO->sleep >> and C<< Future::IO->alarm >> methods.
563              
564             The two methods are combined in one test suite as they are very similar, and
565             neither is long or complicated.
566              
567             =cut
568              
569             sub run_sleep_test
570             {
571             time_about sub {
572 0     0     Future::IO->sleep( 0.2 )->get;
573 0     0 0   }, 0.2, 'Future::IO->sleep( 0.2 ) sleeps 0.2 seconds';
574              
575             time_about sub {
576 0     0     my $f1 = Future::IO->sleep( 0.1 );
577 0           my $f2 = Future::IO->sleep( 0.3 );
578 0           $f1->cancel;
579 0           $f2->get;
580 0           }, 0.3, 'Future::IO->sleep can be cancelled';
581              
582             {
583 0           my $f1 = Future::IO->sleep( 0.1 );
  0            
584 0           my $f2 = Future::IO->sleep( 0.3 );
585              
586 0           is( $f2->await, $f2, '->await returns Future' );
587 0           ok( $f2->is_ready, '$f2 is ready after ->await' );
588 0           ok( $f1->is_ready, '$f1 is also ready after ->await' );
589             }
590              
591             time_about sub {
592 0     0     Future::IO->alarm( time() + 0.2 )->get;
593 0           }, 0.2, 'Future::IO->alarm( now + 0.2 ) sleeps 0.2 seconds';
594             }
595              
596             =head2 read, sysread
597              
598             Tests the C<< Future::IO->sysread >> or C<< Future::IO->sysread >> method.
599              
600             These two test suites are identical other than the name of the method they
601             invoke. The two exist because of the method rename that happened at
602             C version 0.17.
603              
604             =cut
605              
606 0     0 0   sub run_read_test { _run_read_test( 'read' ); }
607 0     0 0   sub run_sysread_test { _run_read_test( 'sysread' ); }
608             sub _run_read_test
609             {
610 0     0     my ( $method ) = @_;
611              
612             # yielding bytes
613             {
614 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
615              
616 0           $wr->autoflush();
617 0           $wr->print( "BYTES" );
618              
619 0           my $f = Future::IO->$method( $rd, 5 );
620              
621 0           is( scalar $f->get, "BYTES", "Future::IO->$method yields bytes from pipe" );
622             }
623              
624             # yielding EOF
625             {
626 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
627 0           $wr->close; undef $wr;
  0            
628              
629 0           my $f = Future::IO->$method( $rd, 1 );
630              
631 0           is( [ $f->get ], [], "Future::IO->$method yields nothing on EOF" );
632             }
633              
634             # TODO: is there a nice portable way we can test for an IO error?
635              
636             # can be cancelled
637             {
638 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
  0            
639              
640 0           $wr->autoflush();
641 0           $wr->print( "BYTES" );
642              
643 0           my $f1 = Future::IO->$method( $rd, 3 );
644 0           my $f2 = Future::IO->$method( $rd, 3 );
645              
646 0           $f1->cancel;
647              
648             # At this point we don't know if $f1 performed its read or not. There's
649             # two possible things we might see from $f2.
650              
651 0           like( scalar $f2->get, qr/^(?:BYT|ES)$/,
652             "Result of second Future::IO->$method after first is cancelled" );
653             }
654             }
655              
656             =head2 write, syswrite
657              
658             Tests the C<< Future::IO->write >> or C<< Future::IO->syswrite >> method.
659              
660             These two test suites are identical other than the name of the method they
661             invoke. The two exist because of the method rename that happened at
662             C version 0.17.
663              
664             =cut
665              
666 0     0 0   sub run_write_test { _run_write_test( 'write' ); }
667 0     0 0   sub run_syswrite_test { _run_write_test( 'syswrite' ); }
668             sub _run_write_test
669             {
670 0     0     my ( $method ) = @_;
671              
672             # success
673             {
674 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
675              
676 0           my $f = Future::IO->$method( $wr, "BYTES" );
677              
678 0           is( scalar $f->get, 5, "Future::IO->$method yields written count" );
679              
680 0           $rd->read( my $buf, 5 );
681 0           is( $buf, "BYTES", "Future::IO->$method wrote bytes" );
682             }
683              
684             # yielding EAGAIN
685             SKIP: {
686 0 0         $^O eq "MSWin32" and skip "MSWin32 doesn't do EAGAIN properly", 2;
687              
688 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
689 0           $rd->blocking( 0 );
690 0           $wr->blocking( 0 );
691              
692             # Attempt to fill the pipe
693 0           my $nblocks;
694 0           for ( 1 .. 256 ) {
695 0 0         defined $wr->$method( "X" x 4096 ) and next;
696 0 0         $! == EAGAIN or
697             die "->$method on write pipe yielded $!";
698              
699 0           $nblocks = $_;
700 0           goto got_EAGAIN;
701             }
702 0           skip "Didn't get EAGAIN despite 256 writes of 4Ki on pipe", 2;
703 0           got_EAGAIN:
704              
705             # clear the error on the filehandle to stop perl printing a warning
706             $wr->clearerr;
707              
708 0           my $f = Future::IO->$method( $wr, "more" );
709              
710 0           ok( !$f->is_ready, '$f is still pending' );
711              
712             # Now make some space
713 0           $rd->read( my $buf, 4096 ) for $nblocks;
714              
715 0           is( scalar $f->get, 4, "Future::IO->$method yields written count" );
716             }
717              
718             # yielding EPIPE
719             {
720 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
721 0           $rd->close; undef $rd;
  0            
722              
723 0           local $SIG{PIPE} = 'IGNORE';
724              
725 0           my $f = Future::IO->$method( $wr, "BYTES" );
726              
727 0           ok( !eval { $f->get }, "Future::IO->$method fails on EPIPE" );
  0            
728              
729 0           is( [ $f->failure ],
730             [ "syswrite: $errstr_EPIPE\n", syswrite => $wr, $errstr_EPIPE ],
731             "Future::IO->$method failure for EPIPE" );
732             }
733              
734             # can be cancelled
735             {
736 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
  0            
737              
738 0           my $f1 = Future::IO->$method( $wr, "BY" );
739 0           my $f2 = Future::IO->$method( $wr, "TES" );
740              
741 0           $f1->cancel;
742              
743 0           is( scalar $f2->get, 3, "Future::IO->$method after cancelled one still works" );
744              
745 0           $rd->read( my $buf, 3 );
746              
747             # At this point we don't know if $f1 performed its write or not. There's
748             # two possible things we might see from the buffer. Either way, the
749             # presence of a 'T' means that $f2 ran.
750              
751 0           like( $buf, qr/^(?:BYT|TES)$/,
752             "A second Future::IO->$method takes place after first is cancelled" );
753             }
754             }
755              
756             =head2 waitpid
757              
758             Tests the C<< Future::IO->waitpid >> method.
759              
760             =cut
761              
762             sub run_waitpid_test
763             {
764             # pre-exit
765             {
766 0 0         defined( my $pid = fork() ) or die "Unable to fork() - $!";
767 0 0         if( $pid == 0 ) {
768             # child
769 0           exit 3;
770             }
771              
772 0           sleep 0.1;
773              
774 0           my $f = Future::IO->waitpid( $pid );
775 0           is( scalar $f->get, ( 3 << 8 ), 'Future::IO->waitpid yields child wait status for pre-exit' );
776             }
777              
778             # post-exit
779             {
780 0 0   0 0   defined( my $pid = fork() ) or die "Unable to fork() - $!";
  0            
  0            
781 0 0         if( $pid == 0 ) {
782             # child
783 0           sleep 0.1;
784 0           exit 4;
785             }
786              
787 0           my $f = Future::IO->waitpid( $pid );
788 0           is( scalar $f->get, ( 4 << 8 ), 'Future::IO->waitpid yields child wait status for post-exit' );
789             }
790             }
791              
792             =head1 AUTHOR
793              
794             Paul Evans
795              
796             =cut
797              
798             0x55AA;