File Coverage

blib/lib/Test/Future/IO/Impl.pm
Criterion Covered Total %
statement 29 284 10.2
branch 0 92 0.0
condition 0 7 0.0
subroutine 10 32 31.2
pod 1 15 6.6
total 40 430 9.3


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.20;
7              
8 1     1   367330 use v5.14;
  1         5  
9 1     1   11 use warnings;
  1         2  
  1         66  
10              
11 1     1   6 use Test2::V0;
  1         2  
  1         10  
12 1     1   1690 use Test2::API ();
  1         3  
  1         32  
13              
14 1     1   5 use Errno qw( EINVAL EPIPE );
  1         2  
  1         182  
15 1     1   709 use IO::Handle;
  1         7875  
  1         71  
16 1     1   578 use IO::Poll qw( POLLIN POLLOUT POLLHUP POLLERR );
  1         1073  
  1         112  
17 1         300 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   806 );
  1         5154  
21 1     1   9 use Time::HiRes qw( sleep time );
  1         2  
  1         9  
22              
23 1     1   92 use Exporter 'import';
  1         2  
  1         5182  
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              
249             # POLLOUT
250             {
251 0 0   0 0   pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
252              
253 0           my $f = Future::IO->poll( $wr, POLLOUT );
254              
255 0           is( scalar $f->get, POLLOUT, "Future::IO->poll(POLLOUT) yields POLLOUT on writable filehandle" );
256              
257 0           my $f1 = Future::IO->poll( $wr, POLLOUT );
258 0           my $f2 = Future::IO->poll( $wr, POLLOUT );
259              
260 0           is( [ scalar $f1->get, scalar $f2->get ], [ POLLOUT, POLLOUT ],
261             'Future::IO->poll(POLLOUT) can enqueue two POLLOUT tests' );
262             }
263              
264             # POLLIN+POLLOUT at once
265             {
266 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
267              
268 0           $wr->autoflush();
269 0           $wr->print( "BYTES" );
270              
271 0           my ( $frd, $fwr );
272              
273             # IN+OUT on reading end
274 0           $frd = Future::IO->poll( $rd, POLLIN );
275 0           $fwr = Future::IO->poll( $rd, POLLOUT );
276              
277 0           is( scalar $frd->get, POLLIN, "Future::IO->poll(POLLIN) yields POLLIN on readable with simultaneous POLLOUT" );
278             # Don't assert on what $fwr saw here, as OSes/impls might differ
279 0           $fwr->cancel;
280              
281             # IN+OUT on writing end
282 0           $frd = Future::IO->poll( $wr, POLLIN );
283 0           $fwr = Future::IO->poll( $wr, POLLOUT );
284              
285 0           is( scalar $fwr->get, POLLOUT, "Future::IO->poll(POLLOUT) yields POLLOUT on writable with simultaneous POLLIN" );
286             # Don't assert on what $frd saw here, as OSes/impls might differ
287 0           $frd->cancel;
288             }
289              
290             # POLLIN doesn't fire accidentally on POLLOUT-only handle
291             {
292 0           require Socket;
  0            
  0            
293 0           require IO::Socket::UNIX;
294              
295 0 0         my ( $s1, $s2 ) = IO::Socket::UNIX->socketpair( Socket::PF_UNIX, Socket::SOCK_STREAM, 0 )
296             or last; # some OSes e.g. Win32 cannot do PF_UNIX socketpairs
297              
298             # Try both orders;
299 0           foreach my $first (qw( IN OUT )) {
300 0           my ( $fin, $fout );
301 0 0         $fin = Future::IO->poll( $s1, POLLIN ) if $first eq "IN";
302 0           $fout = Future::IO->poll( $s1, POLLOUT );
303 0 0         $fin = Future::IO->poll( $s1, POLLIN ) if $first eq "OUT";
304              
305 0           is( scalar $fout->get, POLLOUT, "Future::IO->poll(POLLOUT) yields POLLOUT on writable $first first" );
306 0           ok( !$fin->is_ready, "Future::IO->poll(POLLIN) remains pending on writeable $first first" );
307             }
308             }
309             }
310              
311             sub run_poll_test
312             {
313 0     0 0   run_poll_no_hup_test();
314              
315             # POLLHUP
316             {
317             # closing the writing end of a pipe puts the reading end at hangup condition
318 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
319 0           close $wr;
320              
321 0           my $f = Future::IO->poll( $rd, POLLIN|POLLHUP );
322              
323 0           is( ( scalar $f->get ) & POLLHUP, POLLHUP,
324             "Future::IO->poll(POLLIN) yields at least POLLHUP on hangup-in filehandle" );
325             }
326              
327             # POLLERR
328             {
329             # closing the reading end of a pipe puts the writing end at error condition, because EPIPE
330 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
  0            
331 0           close $rd;
332              
333 0           my $f = Future::IO->poll( $wr, POLLOUT|POLLHUP );
334              
335             # We expect at least one of POLLERR or POLLHUP, we might also see POLLOUT
336             # well but lets not care about that
337 0           my $got_revents = $f->get;
338 0           ok( $got_revents & (POLLERR|POLLHUP),
339             "Future::IO->poll(POLLOUT) yields POLLERR or POLLHUP on hangup-out filehandle" );
340             }
341             }
342              
343             =head2 recv, recvfrom
344              
345             I
346              
347             Tests the C<< Future::IO->recv >> and C<< Future::IO->recvfrom >> methods.
348              
349             =cut
350              
351             # Getting a read/write socket pair which has working addresses is nontrivial.
352             # AF_UNIX sockets created by socketpair() literally have no addresses. AF_INET
353             # sockets would always have an address, but socketpair() can't create
354             # connected AF_INET pairs on most platforms. Grr.
355             # We'll make our own socketpair-alike that does.
356             sub _socketpair_INET_DGRAM
357             {
358 0     0     my ( $connected ) = @_;
359 0   0       $connected //= 1;
360              
361 0           require IO::Socket::INET;
362              
363             # The IO::Socket constructors are unhelpful to us here; we'll do it ourselves
364 0 0         my $rd = IO::Socket::INET->new
365             ->socket( AF_INET, SOCK_DGRAM, 0 ) or die "Cannot socket rd - $!";
366 0 0         $rd->bind( pack_sockaddr_in( 0, INADDR_LOOPBACK ) ) or die "Cannot bind rd - $!";
367              
368 0           my $wr = IO::Socket::INET->new
369             ->socket( AF_INET, SOCK_DGRAM, 0 );
370 0 0 0       $wr->connect( $rd->sockname ) or die "Cannot connect wr - $!"
371             if $connected;
372              
373 0           return ( $rd, $wr );
374             }
375              
376 0     0 0   sub run_recv_test { _run_recv_test( 'recv', 0 ); }
377 0     0 0   sub run_recvfrom_test { _run_recv_test( 'recvfrom', 1 ); }
378             sub _run_recv_test
379             {
380 0     0     my ( $method, $expect_fromaddr ) = @_;
381              
382             # yielding bytes
383             {
384 0           my ( $rd, $wr ) = _socketpair_INET_DGRAM();
  0            
385              
386 0           $wr->autoflush();
387 0           $wr->send( "BYTES" );
388              
389 0           my $f = Future::IO->$method( $rd, 5 );
390              
391 0           is( scalar $f->get, "BYTES", "Future::IO->$method yields bytes from socket" );
392             # We can't know exactly what address it will be but
393 0           my $fromaddr = ( $f->get )[1];
394 0 0         ok( defined $fromaddr, "Future::IO->$method also yields a fromaddr" )
395             if $expect_fromaddr;
396 0 0         is( sockaddr_family( $fromaddr ), AF_INET, "Future::IO->$method fromaddr is valid AF_INET address" )
397             if $expect_fromaddr;
398             }
399              
400             # From here onwards we don't need working sockaddr/peeraddr so we can just
401             # use simpler IO::Socket::UNIX->socketpair instead
402              
403 0 0         return if $^O eq "MSWin32";
404              
405 0           require IO::Socket::UNIX;
406              
407             # yielding EOF
408             {
409 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
410             or die "Cannot socketpair() - $!";
411 0           $wr->close; undef $wr;
  0            
412              
413 0           my $f = Future::IO->$method( $rd, 1 );
414              
415 0           is ( [ $f->get ], [], "Future::IO->$method yields nothing on EOF" );
416             }
417              
418             # can be cancelled
419             {
420 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
  0            
  0            
421             or die "Cannot socketpair() - $!";
422              
423 0           $wr->autoflush();
424 0           $wr->send( "BYTES" );
425              
426 0           my $f1 = Future::IO->$method( $rd, 3 );
427 0           my $f2 = Future::IO->$method( $rd, 3 );
428              
429 0           $f1->cancel;
430              
431             # At this point we don't know if $f1 performed its recv or not. There's
432             # two possible things we might see from $f2.
433              
434 0           like( scalar $f2->get, qr/^(?:BYT|ES)$/,
435             "Result of second Future::IO->$method after first is cancelled" );
436             }
437             }
438              
439             =head2 send
440              
441             I
442              
443             Tests the C<< Future::IO->send >> method.
444              
445             =cut
446              
447             sub run_send_test
448             {
449             # success
450             {
451             # An unconnected socketpair to prove that ->send used the correct address later on
452 0     0 0   my ( $rd, $wr ) = _socketpair_INET_DGRAM( 0 );
  0            
453              
454 0           my $f = Future::IO->send( $wr, "BYTES", 0, $rd->sockname );
455              
456 0           is( scalar $f->get, 5, 'Future::IO->send yields sent count' );
457              
458 0           $rd->recv( my $buf, 5 );
459 0           is( $buf, "BYTES", 'Future::IO->send sent bytes' );
460             }
461              
462             # From here onwards we don't need working sockaddr/peeraddr so we can just
463             # use simpler IO::Socket::UNIX->socketpair instead
464              
465 0 0         return if $^O eq "MSWin32";
466              
467 0           require IO::Socket::UNIX;
468              
469             # yielding EAGAIN
470             SKIP: {
471 0 0         $^O eq "MSWin32" and skip "MSWin32 doesn't do EAGAIN properly", 2;
472              
473 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
474             or die "Cannot socketpair() - $!";
475 0           $wr->blocking( 0 );
476              
477             # Attempt to fill the buffer
478 0           $wr->write( "X" x 4096 ) for 1..256;
479              
480 0           my $f = Future::IO->send( $wr, "more" );
481              
482 0           ok( !$f->is_ready, '$f is still pending' );
483              
484             # Now make some space. We need to drain it quite a lot for mechanisms
485             # like ppoll() to be happy that the socket is actually writable
486 0           $rd->blocking( 0 );
487 0           $rd->read( my $buf, 4096 ) for 1..256;
488              
489 0           is( scalar $f->get, 4, 'Future::IO->send yields written count' );
490             }
491              
492             # yielding EPIPE
493             {
494 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
  0            
495             or die "Cannot socketpair() - $!";
496 0           $rd->close; undef $rd;
  0            
497              
498 0           local $SIG{PIPE} = 'IGNORE';
499              
500 0           my $f = Future::IO->send( $wr, "BYTES" );
501              
502 0           $f->await;
503 0           ok( $f->is_ready, '->send future is now ready after EPIPE' );
504              
505             # Sometimes we get EPIPE out of a send(2) system call (e.g Linux).
506             # Sometimes we get a croak out of IO::Socket->send itself because it
507             # checked getpeername() and found it missing (e.g. most BSDs). We
508             # shouldn't be overly concerned with _what_ the failure is, only that
509             # it failed somehow.
510 0           ok( scalar $f->failure, 'Future::IO->send failed after peer closed' );
511             }
512              
513             # can be cancelled
514             {
515 0 0         my ( $rd, $wr ) = IO::Socket::UNIX->socketpair( AF_UNIX, SOCK_STREAM, PF_UNSPEC )
  0            
  0            
516             or die "Cannot socketpair() - $!";
517              
518 0           my $f1 = Future::IO->send( $wr, "BY" );
519 0           my $f2 = Future::IO->send( $wr, "TES" );
520              
521 0           $f1->cancel;
522              
523 0           is( scalar $f2->get, 3, 'Future::IO->send after cancelled one still works' );
524              
525 0           $rd->read( my $buf, 3 );
526              
527             # At this point we don't know if $f1 performed its send or not. There's
528             # two possible things we might see from the buffer. Either way, the
529             # presence of a 'T' means that $f2 ran.
530              
531 0           like( $buf, qr/^(?:BYT|TES)$/,
532             "A second Future::IO->send takes place after first is cancelled" );
533             }
534             }
535              
536             =head2 sleep
537              
538             Tests the C<< Future::IO->sleep >> and C<< Future::IO->alarm >> methods.
539              
540             The two methods are combined in one test suite as they are very similar, and
541             neither is long or complicated.
542              
543             =cut
544              
545             sub run_sleep_test
546             {
547             time_about sub {
548 0     0     Future::IO->sleep( 0.2 )->get;
549 0     0 0   }, 0.2, 'Future::IO->sleep( 0.2 ) sleeps 0.2 seconds';
550              
551             time_about sub {
552 0     0     my $f1 = Future::IO->sleep( 0.1 );
553 0           my $f2 = Future::IO->sleep( 0.3 );
554 0           $f1->cancel;
555 0           $f2->get;
556 0           }, 0.3, 'Future::IO->sleep can be cancelled';
557              
558             {
559 0           my $f1 = Future::IO->sleep( 0.1 );
  0            
560 0           my $f2 = Future::IO->sleep( 0.3 );
561              
562 0           is( $f2->await, $f2, '->await returns Future' );
563 0           ok( $f2->is_ready, '$f2 is ready after ->await' );
564 0           ok( $f1->is_ready, '$f1 is also ready after ->await' );
565             }
566              
567             time_about sub {
568 0     0     Future::IO->alarm( time() + 0.2 )->get;
569 0           }, 0.2, 'Future::IO->alarm( now + 0.2 ) sleeps 0.2 seconds';
570             }
571              
572             =head2 read, sysread
573              
574             Tests the C<< Future::IO->sysread >> or C<< Future::IO->sysread >> method.
575              
576             These two test suites are identical other than the name of the method they
577             invoke. The two exist because of the method rename that happened at
578             C version 0.17.
579              
580             =cut
581              
582 0     0 0   sub run_read_test { _run_read_test( 'read' ); }
583 0     0 0   sub run_sysread_test { _run_read_test( 'sysread' ); }
584             sub _run_read_test
585             {
586 0     0     my ( $method ) = @_;
587              
588             # yielding bytes
589             {
590 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
591              
592 0           $wr->autoflush();
593 0           $wr->print( "BYTES" );
594              
595 0           my $f = Future::IO->$method( $rd, 5 );
596              
597 0           is( scalar $f->get, "BYTES", "Future::IO->$method yields bytes from pipe" );
598             }
599              
600             # yielding EOF
601             {
602 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
603 0           $wr->close; undef $wr;
  0            
604              
605 0           my $f = Future::IO->$method( $rd, 1 );
606              
607 0           is( [ $f->get ], [], "Future::IO->$method yields nothing on EOF" );
608             }
609              
610             # TODO: is there a nice portable way we can test for an IO error?
611              
612             # can be cancelled
613             {
614 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
  0            
615              
616 0           $wr->autoflush();
617 0           $wr->print( "BYTES" );
618              
619 0           my $f1 = Future::IO->$method( $rd, 3 );
620 0           my $f2 = Future::IO->$method( $rd, 3 );
621              
622 0           $f1->cancel;
623              
624             # At this point we don't know if $f1 performed its read or not. There's
625             # two possible things we might see from $f2.
626              
627 0           like( scalar $f2->get, qr/^(?:BYT|ES)$/,
628             "Result of second Future::IO->$method after first is cancelled" );
629             }
630             }
631              
632             =head2 write, syswrite
633              
634             Tests the C<< Future::IO->write >> or C<< Future::IO->syswrite >> method.
635              
636             These two test suites are identical other than the name of the method they
637             invoke. The two exist because of the method rename that happened at
638             C version 0.17.
639              
640             =cut
641              
642 0     0 0   sub run_write_test { _run_write_test( 'write' ); }
643 0     0 0   sub run_syswrite_test { _run_write_test( 'syswrite' ); }
644             sub _run_write_test
645             {
646 0     0     my ( $method ) = @_;
647              
648             # success
649             {
650 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
651              
652 0           my $f = Future::IO->$method( $wr, "BYTES" );
653              
654 0           is( scalar $f->get, 5, "Future::IO->$method yields written count" );
655              
656 0           $rd->read( my $buf, 5 );
657 0           is( $buf, "BYTES", "Future::IO->$method wrote bytes" );
658             }
659              
660             # yielding EAGAIN
661             SKIP: {
662 0 0         $^O eq "MSWin32" and skip "MSWin32 doesn't do EAGAIN properly", 2;
663              
664 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
665 0           $wr->blocking( 0 );
666              
667             # Attempt to fill the pipe
668 0           $wr->$method( "X" x 4096 ) for 1..256;
669             # clear the error on the filehandle to stop perl printing a warning
670 0           $wr->clearerr;
671              
672 0           my $f = Future::IO->$method( $wr, "more" );
673              
674 0           ok( !$f->is_ready, '$f is still pending' );
675              
676             # Now make some space
677 0           $rd->read( my $buf, 4096 );
678              
679 0           is( scalar $f->get, 4, "Future::IO->$method yields written count" );
680             }
681              
682             # yielding EPIPE
683             {
684 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
685 0           $rd->close; undef $rd;
  0            
686              
687 0           local $SIG{PIPE} = 'IGNORE';
688              
689 0           my $f = Future::IO->$method( $wr, "BYTES" );
690              
691 0           ok( !eval { $f->get }, "Future::IO->$method fails on EPIPE" );
  0            
692              
693 0           is( [ $f->failure ],
694             [ "syswrite: $errstr_EPIPE\n", syswrite => $wr, $errstr_EPIPE ],
695             "Future::IO->$method failure for EPIPE" );
696             }
697              
698             # can be cancelled
699             {
700 0 0         pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  0            
  0            
701              
702 0           my $f1 = Future::IO->$method( $wr, "BY" );
703 0           my $f2 = Future::IO->$method( $wr, "TES" );
704              
705 0           $f1->cancel;
706              
707 0           is( scalar $f2->get, 3, "Future::IO->$method after cancelled one still works" );
708              
709 0           $rd->read( my $buf, 3 );
710              
711             # At this point we don't know if $f1 performed its write or not. There's
712             # two possible things we might see from the buffer. Either way, the
713             # presence of a 'T' means that $f2 ran.
714              
715 0           like( $buf, qr/^(?:BYT|TES)$/,
716             "A second Future::IO->$method takes place after first is cancelled" );
717             }
718             }
719              
720             =head2 waitpid
721              
722             Tests the C<< Future::IO->waitpid >> method.
723              
724             =cut
725              
726             sub run_waitpid_test
727             {
728             # pre-exit
729             {
730 0 0         defined( my $pid = fork() ) or die "Unable to fork() - $!";
731 0 0         if( $pid == 0 ) {
732             # child
733 0           exit 3;
734             }
735              
736 0           sleep 0.1;
737              
738 0           my $f = Future::IO->waitpid( $pid );
739 0           is( scalar $f->get, ( 3 << 8 ), 'Future::IO->waitpid yields child wait status for pre-exit' );
740             }
741              
742             # post-exit
743             {
744 0 0   0 0   defined( my $pid = fork() ) or die "Unable to fork() - $!";
  0            
  0            
745 0 0         if( $pid == 0 ) {
746             # child
747 0           sleep 0.1;
748 0           exit 4;
749             }
750              
751 0           my $f = Future::IO->waitpid( $pid );
752 0           is( scalar $f->get, ( 4 << 8 ), 'Future::IO->waitpid yields child wait status for post-exit' );
753             }
754             }
755              
756             =head1 AUTHOR
757              
758             Paul Evans
759              
760             =cut
761              
762             0x55AA;