File Coverage

blib/lib/Test/Future/IO/Impl.pm
Criterion Covered Total %
statement 132 147 89.8
branch 18 44 40.9
condition 1 3 33.3
subroutine 19 20 95.0
pod 1 8 12.5
total 171 222 77.0


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 -- leonerd@leonerd.org.uk
5              
6             package Test::Future::IO::Impl 0.12;
7              
8 6     6   340730 use v5.14;
  6         70  
9 6     6   31 use warnings;
  6         11  
  6         156  
10              
11 6     6   31 use Test::More;
  6         10  
  6         31  
12 6     6   1527 use Test::Builder;
  6         11  
  6         156  
13              
14 6     6   2237 use Errno qw( EINVAL EPIPE );
  6         6892  
  6         641  
15 6     6   3241 use IO::Handle;
  6         37428  
  6         283  
16 6     6   2890 use Socket qw( pack_sockaddr_in INADDR_LOOPBACK );
  6         18118  
  6         920  
17 6     6   2520 use Time::HiRes qw( time );
  6         6316  
  6         33  
18              
19 6     6   1125 use Exporter 'import';
  6         14  
  6         11201  
20             our @EXPORT = qw( run_tests );
21              
22             =head1 NAME
23              
24             C - acceptance tests for C implementations
25              
26             =head1 SYNOPSIS
27              
28             use Test::More;
29             use Test::Future::IO::Impl;
30              
31             use Future::IO;
32             use Future::IO::Impl::MyNewImpl;
33              
34             run_tests 'sleep';
35              
36             done_testing;
37              
38             =head1 DESCRIPTION
39              
40             This module contains a collection of acceptance tests for implementations of
41             L.
42              
43             =cut
44              
45             =head1 FUNCTIONS
46              
47             =cut
48              
49             my $errstr_EPIPE = do {
50             # On MSWin32 we don't get EPIPE, but EINVAL
51             local $! = $^O eq "MSWin32" ? EINVAL : EPIPE; "$!";
52             };
53              
54             my $errstr_ECONNREFUSED = do {
55             local $! = Errno::ECONNREFUSED; "$!";
56             };
57              
58             sub time_about(&@)
59             {
60 3     3 0 16 my ( $code, $want_time, $name ) = @_;
61 3         18 my $test = Test::Builder->new;
62              
63 3         38 my $t0 = time();
64 3         13 $code->();
65 3         130 my $t1 = time();
66              
67 3         16 my $got_time = $t1 - $t0;
68 3 50 33     64 $test->ok(
69             $got_time >= $want_time * 0.9 && $got_time <= $want_time * 1.5, $name
70             ) or
71             $test->diag( sprintf "Test took %.3f seconds", $got_time );
72             }
73              
74             =head2 run_tests
75              
76             run_tests @suitenames
77              
78             Runs a collection of tests against C. It is expected that the
79             caller has already loaded the specific implementation module to be tested
80             against before this function is called.
81              
82             =cut
83              
84             sub run_tests
85             {
86 5     5 1 411 foreach my $test ( @_ ) {
87 5 50       83 my $code = __PACKAGE__->can( "run_${test}_test" )
88             or die "Unrecognised test suite name $test";
89 5         21 __PACKAGE__->$code();
90             }
91             }
92              
93             =head1 TEST SUITES
94              
95             The following test suite names may be passed to the L function:
96              
97             =cut
98              
99             =head2 accept
100              
101             Tests the C<< Future::IO->accept >> method.
102              
103             =cut
104              
105             sub run_accept_test
106             {
107 1     1 0 505 require IO::Socket::INET;
108              
109 1 50       8428 my $serversock = IO::Socket::INET->new(
110             Type => Socket::SOCK_STREAM(),
111             LocalAddr => "localhost",
112             LocalPort => 0,
113             Listen => 1,
114             ) or die "Cannot socket()/listen() - $@";
115              
116 1         926 $serversock->blocking( 0 );
117              
118 1         37 my $f = Future::IO->accept( $serversock );
119              
120             # Some platforms have assigned 127.0.0.1 here; others have left 0.0.0.0
121             # If it's still 0.0.0.0, then guess that maybe connecting to 127.0.0.1 will
122             # work
123 1 50       131 my $sockname = ( $serversock->sockhost ne "0.0.0.0" )
124             ? $serversock->sockname
125             : pack_sockaddr_in( $serversock->sockport, INADDR_LOOPBACK );
126              
127 1 50       87 my $clientsock = IO::Socket::INET->new(
128             Type => Socket::SOCK_STREAM(),
129             ) or die "Cannot socket() - $@";
130 1 50       240 $clientsock->connect( $sockname ) or die "Cannot connect() - $@";
131              
132 1         166 my $acceptedsock = $f->get;
133              
134 1         22 ok( $clientsock->peername eq $acceptedsock->sockname, 'Accepted socket address matches' );
135             }
136              
137             =head2 connect
138              
139             Tests the C<< Future::IO->connect >> method.
140              
141             =cut
142              
143             sub run_connect_test
144             {
145 1     1 0 494 require IO::Socket::INET;
146              
147 1 50       8534 my $serversock = IO::Socket::INET->new(
148             Type => Socket::SOCK_STREAM(),
149             LocalAddr => "localhost",
150             LocalPort => 0,
151             Listen => 1,
152             ) or die "Cannot socket()/listen() - $@";
153              
154             # Some platforms have assigned 127.0.0.1 here; others have left 0.0.0.0
155             # If it's still 0.0.0.0, then guess that maybe connecting to 127.0.0.1 will
156             # work
157 1 50       929 my $sockname = ( $serversock->sockhost ne "0.0.0.0" )
158             ? $serversock->sockname
159             : pack_sockaddr_in( $serversock->sockport, INADDR_LOOPBACK );
160              
161             # ->connect success
162             {
163 1 50       60 my $clientsock = IO::Socket::INET->new(
  1         7  
164             Type => Socket::SOCK_STREAM(),
165             ) or die "Cannot socket() - $@";
166 1         196 $clientsock->blocking( 0 );
167              
168 1         28 my $f = Future::IO->connect( $clientsock, $sockname );
169              
170 1         144 $f->get;
171              
172 1         21 my $acceptedsock = $serversock->accept;
173 1         155 ok( $clientsock->peername eq $acceptedsock->sockname, 'Accepted socket address matches' );
174             }
175              
176 1         783 $serversock->close;
177 1         36 undef $serversock;
178              
179             # ->connect fails
180             {
181 1 50       2 my $clientsock = IO::Socket::INET->new(
  1         7  
182             Type => Socket::SOCK_STREAM(),
183             ) or die "Cannot socket() - $@";
184 1         186 $clientsock->blocking( 0 );
185              
186 1         19 my $f = Future::IO->connect( $clientsock, $sockname );
187              
188 1         460 ok( !eval { $f->get; 1 }, 'Future::IO->connect fails on closed server' );
  1         4  
  0         0  
189              
190 1         400 is_deeply( [ $f->failure ],
191             [ "connect: $errstr_ECONNREFUSED\n", connect => $clientsock, $errstr_ECONNREFUSED ],
192             'Future::IO->connect failure' );
193             }
194             }
195              
196             =head2 sleep
197              
198             Tests the C<< Future::IO->sleep >> method.
199              
200             =cut
201              
202             sub run_sleep_test
203             {
204 1     1 0 8 my $test = Test::Builder->new;
205              
206             time_about sub {
207 1     1   8 Future::IO->sleep( 0.2 )->get;
208 1         18 }, 0.2, 'Future::IO->sleep( 0.2 ) sleeps 0.2 seconds';
209              
210             time_about sub {
211 1     1   9 my $f1 = Future::IO->sleep( 0.1 );
212 1         7 my $f2 = Future::IO->sleep( 0.3 );
213 1         17 $f1->cancel;
214 1         54 $f2->get;
215 1         1232 }, 0.3, 'Future::IO->sleep can be cancelled';
216              
217             {
218 1         939 my $f1 = Future::IO->sleep( 0.1 );
  1         12  
219 1         7 my $f2 = Future::IO->sleep( 0.3 );
220              
221 1         23 is( $f2->await, $f2, '->await returns Future' );
222 1         772 ok( $f2->is_ready, '$f2 is ready after ->await' );
223 1         490 ok( $f1->is_ready, '$f1 is also ready after ->await' );
224             }
225              
226             time_about sub {
227 1     1   12 Future::IO->alarm( time() + 0.2 )->get;
228 1         532 }, 0.2, 'Future::IO->alarm( now + 0.2 ) sleeps 0.2 seconds';
229             }
230              
231             =head2 sysread
232              
233             Tests the C<< Future::IO->sysread >> method.
234              
235             =cut
236              
237             sub run_sysread_test
238             {
239             # ->sysread yielding bytes
240             {
241 1 50       40 pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
242              
243 1         14 $wr->autoflush();
244 1         68 $wr->print( "BYTES" );
245              
246 1         33 my $f = Future::IO->sysread( $rd, 5 );
247              
248 1         150 is( scalar $f->get, "BYTES", 'Future::IO->sysread yields bytes from pipe' );
249             }
250              
251             # ->sysread yielding EOF
252             {
253 1 50   1 0 4 pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  1         35  
254 1         12 $wr->close; undef $wr;
  1         16  
255              
256 1         7 my $f = Future::IO->sysread( $rd, 1 );
257              
258 1         86 is_deeply( [ $f->get ], [], 'Future::IO->sysread yields nothing on EOF' );
259             }
260              
261             # TODO: is there a nice portable way we can test for an IO error?
262              
263             # ->sysread can be cancelled
264             {
265 1 50       508 pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  1         586  
  1         32  
266              
267 1         5 $wr->autoflush();
268 1         40 $wr->print( "BYTES" );
269              
270 1         23 my $f1 = Future::IO->sysread( $rd, 3 );
271 1         85 my $f2 = Future::IO->sysread( $rd, 3 );
272              
273 1         100 $f1->cancel;
274              
275 1         77 is( scalar $f2->get, "BYT", 'Future::IO->sysread can be cancelled' );
276             }
277             }
278              
279             =head2 syswrite
280              
281             Tests the C<< Future::IO->syswrite >> method.
282              
283             =cut
284              
285             sub run_syswrite_test
286             {
287             # ->syswrite success
288             {
289 1 50   1 0 13 pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  1         45  
290              
291 1         10 my $f = Future::IO->syswrite( $wr, "BYTES" );
292              
293 1         142 is( scalar $f->get, 5, 'Future::IO->syswrite yields written count' );
294              
295 1         506 $rd->read( my $buf, 5 );
296 1         32 is( $buf, "BYTES", 'Future::IO->syswrite wrote bytes' );
297             }
298              
299             # ->syswrite yielding EAGAIN
300             SKIP: {
301 1 50       8 $^O eq "MSWin32" and skip "MSWin32 doesn't do EAGAIN properly", 2;
302              
303 1 50       33 pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
304 1         17 $wr->blocking( 0 );
305              
306             # Attempt to fill the pipe
307 1         13 $wr->syswrite( "X" x 4096 ) for 1..256;
308              
309 1         2753 my $f = Future::IO->syswrite( $wr, "more" );
310              
311 1         86 ok( !$f->is_ready, '$f is still pending' );
312              
313             # Now make some space
314 1         337 $rd->read( my $buf, 4096 );
315              
316 1         23 is( scalar $f->get, 4, 'Future::IO->syswrite yields written count' );
317             }
318              
319             # ->syswrite yielding EPIPE
320             {
321 1 50       313 pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  1         44  
322 1         14 $rd->close; undef $rd;
  1         20  
323              
324 1         33 local $SIG{PIPE} = 'IGNORE';
325              
326 1         17 my $f = Future::IO->syswrite( $wr, "BYTES" );
327              
328 1         103 ok( !eval { $f->get }, 'Future::IO->syswrite fails on EPIPE' );
  1         3  
329              
330 1         386 is_deeply( [ $f->failure ],
331             [ "syswrite: $errstr_EPIPE\n", syswrite => $wr, $errstr_EPIPE ],
332             'Future::IO->syswrite failure for EPIPE' );
333             }
334              
335             # ->syswrite can be cancelled
336             {
337 1 50       347 pipe my ( $rd, $wr ) or die "Cannot pipe() - $!";
  1         809  
  1         57  
338              
339 1         7 my $f1 = Future::IO->syswrite( $wr, "BY" );
340 1         114 my $f2 = Future::IO->syswrite( $wr, "TES" );
341              
342 1         99 $f1->cancel;
343              
344 1         89 is( scalar $f2->get, 3, 'Future::IO->syswrite after cancelled one still works' );
345              
346 1         334 $rd->read( my $buf, 3 );
347 1         29 is( $buf, "TES", 'Cancelled Future::IO->syswrite did not write bytes' );
348             }
349             }
350              
351             =head2 waitpid
352              
353             Tests the C<< Future::IO->waitpid >> method.
354              
355             =cut
356              
357             sub run_waitpid_test
358             {
359             # pre-exit
360             {
361 0 0         defined( my $pid = fork() ) or die "Unable to fork() - $!";
362 0 0         if( $pid == 0 ) {
363             # child
364 0           exit 3;
365             }
366              
367 0           Time::HiRes::sleep 0.1;
368              
369 0           my $f = Future::IO->waitpid( $pid );
370 0           is( scalar $f->get, ( 3 << 8 ), 'Future::IO->waitpid yields child wait status for pre-exit' );
371             }
372              
373             # post-exit
374             {
375 0 0   0 0   defined( my $pid = fork() ) or die "Unable to fork() - $!";
  0            
  0            
376 0 0         if( $pid == 0 ) {
377             # child
378 0           Time::HiRes::sleep 0.1;
379 0           exit 4;
380             }
381              
382 0           my $f = Future::IO->waitpid( $pid );
383 0           is( scalar $f->get, ( 4 << 8 ), 'Future::IO->waitpid yields child wait status for post-exit' );
384             }
385             }
386              
387             =head1 AUTHOR
388              
389             Paul Evans
390              
391             =cut
392              
393             0x55AA;