1#!/usr/bin/perl
2
3use strict;
4use warnings;
5
6use IO::Async::Test;
7
8use Test::More;
9use Test::Fatal;
10use Test::Identity;
11use Test::Refcount;
12
13use IO::Async::Loop;
14
15use IO::Async::Handle;
16
17use IO::Async::OS;
18
19use Socket qw( AF_INET SOCK_STREAM SOCK_DGRAM SO_TYPE unpack_sockaddr_in );
20
21my $loop = IO::Async::Loop->new_builtin;
22
23testing_loop( $loop );
24
25sub mkhandles
26{
27   my ( $S1, $S2 ) = IO::Async::OS->socketpair or die "Cannot create socket pair - $!";
28
29   # Need sockets in nonblocking mode
30   $S1->blocking( 0 );
31   $S2->blocking( 0 );
32
33   return ( $S1, $S2 );
34}
35
36ok( exception { IO::Async::Handle->new( handle => "Hello" ) }, 'Not a filehandle' );
37
38# Read readiness
39{
40   my ( $S1, $S2 ) = mkhandles;
41   my $fd1 = $S1->fileno;
42
43   my $readready = 0;
44   my @rrargs;
45
46   my $handle = IO::Async::Handle->new(
47      read_handle => $S1,
48      on_read_ready  => sub { @rrargs = @_; $readready = 1 },
49   );
50
51   ok( defined $handle, '$handle defined' );
52   isa_ok( $handle, "IO::Async::Handle", '$handle isa IO::Async::Handle' );
53
54   is( $handle->notifier_name, "r=$fd1", '$handle->notifier_name for read_handle' );
55
56   is_oneref( $handle, '$handle has refcount 1 initially' );
57
58   is( $handle->read_handle,  $S1, '->read_handle returns S1' );
59   is( $handle->read_fileno,  $S1->fileno, '->read_fileno returns fileno(S1)' );
60
61   is( $handle->write_handle, undef, '->write_handle returns undef' );
62
63   ok( $handle->want_readready, 'want_readready true' );
64
65   $loop->add( $handle );
66
67   is_refcount( $handle, 2, '$handle has refcount 2 after adding to Loop' );
68
69   $loop->loop_once( 0.1 ); # nothing happens
70
71   is( $readready,  0, '$readready while idle' );
72
73   $S2->syswrite( "data\n" );
74
75   wait_for { $readready };
76
77   is( $readready,  1, '$readready while readable' );
78   is_deeply( \@rrargs, [ $handle ], 'on_read_ready args while readable' );
79
80   $S1->getline; # ignore return
81
82   $readready = 0;
83   my $new_readready = 0;
84
85   $handle->configure( on_read_ready => sub { $new_readready = 1 } );
86
87   $loop->loop_once( 0.1 ); # nothing happens
88
89   is( $readready,     0, '$readready while idle after on_read_ready replace' );
90   is( $new_readready, 0, '$new_readready while idle after on_read_ready replace' );
91
92   $S2->syswrite( "data\n" );
93
94   wait_for { $new_readready };
95
96   is( $readready,     0, '$readready while readable after on_read_ready replace' );
97   is( $new_readready, 1, '$new_readready while readable after on_read_ready replace' );
98
99   $S1->getline; # ignore return
100
101   ok( exception { $handle->want_writeready( 1 ); },
102       'setting want_writeready with write_handle == undef dies' );
103   ok( !$handle->want_writeready, 'wantwriteready write_handle == undef false' );
104
105   undef @rrargs;
106
107   is_refcount( $handle, 2, '$handle has refcount 2 before removing from Loop' );
108
109   $loop->remove( $handle );
110
111   is_oneref( $handle, '$handle has refcount 1 finally' );
112}
113
114# Write readiness
115{
116   my ( $S1, $S2 ) = mkhandles;
117   my $fd1 = $S1->fileno;
118
119   my $writeready = 0;
120   my @wrargs;
121
122   my $handle = IO::Async::Handle->new(
123      write_handle => $S1,
124      on_write_ready => sub { @wrargs = @_; $writeready = 1 },
125   );
126
127   ok( defined $handle, '$handle defined' );
128   isa_ok( $handle, "IO::Async::Handle", '$handle isa IO::Async::Handle' );
129
130   is( $handle->notifier_name, "w=$fd1", '$handle->notifier_name for write_handle' );
131
132   is_oneref( $handle, '$handle has refcount 1 initially' );
133
134   is( $handle->write_handle, $S1, '->write_handle returns S1' );
135   is( $handle->write_fileno, $S1->fileno, '->write_fileno returns fileno(S1)' );
136
137   is( $handle->read_handle, undef, '->read_handle returns undef' );
138
139   ok( !$handle->want_writeready, 'want_writeready false' );
140
141   $loop->add( $handle );
142
143   is_refcount( $handle, 2, '$handle has refcount 2 after adding to Loop' );
144
145   $loop->loop_once( 0.1 ); # nothing happens
146
147   is( $writeready, 0, '$writeready while idle' );
148
149   $handle->want_writeready( 1 );
150
151   wait_for { $writeready };
152
153   is( $writeready, 1, '$writeready while writeable' );
154   is_deeply( \@wrargs, [ $handle ], 'on_write_ready args while writeable' );
155
156   $writeready = 0;
157   my $new_writeready = 0;
158
159   $handle->configure( on_write_ready => sub { $new_writeready = 1 } );
160
161   wait_for { $new_writeready };
162
163   is( $writeready,     0, '$writeready while writeable after on_write_ready replace' );
164   is( $new_writeready, 1, '$new_writeready while writeable after on_write_ready replace' );
165
166   undef @wrargs;
167
168   is_refcount( $handle, 2, '$handle has refcount 2 before removing from Loop' );
169
170   $loop->remove( $handle );
171
172   is_oneref( $handle, '$handle has refcount 1 finally' );
173}
174
175# Combined handle
176{
177   my ( $S1, $S2 ) = mkhandles;
178   my $fd1 = $S1->fileno;
179
180   my $handle = IO::Async::Handle->new(
181      handle => $S1,
182      on_read_ready  => sub {},
183      on_write_ready => sub {},
184   );
185
186   is( $handle->read_handle,  $S1, '->read_handle returns S1' );
187   is( $handle->write_handle, $S1, '->write_handle returns S1' );
188
189   is( $handle->notifier_name, "rw=$fd1", '$handle->notifier_name for handle' );
190}
191
192# Subclass
193my $sub_readready = 0;
194my $sub_writeready = 0;
195
196{
197   my ( $S1, $S2 ) = mkhandles;
198
199   my $handle = TestHandle->new(
200      handle => $S1,
201   );
202
203   ok( defined $handle, 'subclass $handle defined' );
204   isa_ok( $handle, "IO::Async::Handle", 'subclass $handle isa IO::Async::Handle' );
205
206   is_oneref( $handle, 'subclass $handle has refcount 1 initially' );
207
208   is( $handle->read_handle,  $S1, 'subclass ->read_handle returns S1' );
209   is( $handle->write_handle, $S1, 'subclass ->write_handle returns S1' );
210
211   $loop->add( $handle );
212
213   is_refcount( $handle, 2, 'subclass $handle has refcount 2 after adding to Loop' );
214
215   $S2->syswrite( "data\n" );
216
217   wait_for { $sub_readready };
218
219   is( $sub_readready,  1, '$sub_readready while readable' );
220   is( $sub_writeready, 0, '$sub_writeready while readable' );
221
222   $S1->getline; # ignore return
223   $sub_readready = 0;
224
225   $handle->want_writeready( 1 );
226
227   wait_for { $sub_writeready };
228
229   is( $sub_readready,  0, '$sub_readready while writeable' );
230   is( $sub_writeready, 1, '$sub_writeready while writeable' );
231
232   $loop->remove( $handle );
233}
234
235# Close
236{
237   my ( $S1, $S2 ) = mkhandles;
238
239   my $closed = 0;
240
241   my $handle = IO::Async::Handle->new(
242      read_handle => $S1,
243      want_writeready => 0,
244      on_read_ready => sub {},
245      on_closed => sub { $closed = 1 },
246   );
247
248   $loop->add( $handle );
249
250   my $close_future = $handle->new_close_future;
251
252   my $closed_by_future;
253   $close_future->on_done( sub { $closed_by_future++ } );
254
255   $handle->close;
256
257   is( $closed, 1, '$closed after ->close' );
258
259   ok( $close_future->is_ready, '$close_future is now ready' );
260   is( $closed_by_future, 1, '$closed_by_future after ->close' );
261
262   # removed itself
263}
264
265# Close read/write
266{
267   my ( $Srd1, $Srd2 ) = mkhandles;
268   my ( $Swr1, $Swr2 ) = mkhandles;
269
270   local $SIG{PIPE} = "IGNORE";
271
272   my $readready  = 0;
273   my $writeready = 0;
274
275   my $closed = 0;
276
277   my $handle = IO::Async::Handle->new(
278      read_handle  => $Srd1,
279      write_handle => $Swr1,
280      on_read_ready  => sub { $readready++ },
281      on_write_ready => sub { $writeready++ },
282      on_closed      => sub { $closed++ },
283      want_writeready => 1,
284   );
285
286   $loop->add( $handle );
287
288   $handle->close_read;
289
290   wait_for { $writeready };
291   is( $writeready, 1, '$writeready after ->close_read' );
292
293   $handle->write_handle->syswrite( "Still works\n" );
294   is( $Swr2->getline, "Still works\n", 'write handle still works' );
295
296   is( $closed, 0, 'not $closed after ->close_read' );
297
298   is( $handle->loop, $loop, 'Handle still member of Loop after ->close_read' );
299
300   ( $Srd1, $Srd2 ) = mkhandles;
301
302   $handle->configure( read_handle => $Srd1 );
303
304   $handle->close_write;
305
306   $Srd2->syswrite( "Also works\n" );
307
308   wait_for { $readready };
309   is( $readready, 1, '$readready after ->close_write' );
310
311   is( $handle->read_handle->getline, "Also works\n", 'read handle still works' );
312   is( $Swr2->getline, undef, 'sysread from EOF write handle' );
313
314   is( $handle->loop, $loop, 'Handle still member of Loop after ->close_write' );
315
316   is( $closed, 0, 'not $closed after ->close_read' );
317
318   $handle->close_read;
319
320   is( $closed, 1, '$closed after ->close_read + ->close_write' );
321
322   is( $handle->loop, undef, '$handle no longer member of Loop' );
323}
324
325# Late-binding of handle
326{
327   my $readready;
328   my $writeready;
329
330   my $handle = IO::Async::Handle->new(
331      want_writeready => 0,
332      on_read_ready  => sub { $readready  = 1 },
333      on_write_ready => sub { $writeready = 1 },
334   );
335
336   ok( defined $handle, '$handle defined' );
337
338   ok( !defined $handle->read_handle,  '->read_handle not defined' );
339   ok( !defined $handle->write_handle, '->write_handle not defined' );
340
341   is_oneref( $handle, '$handle latebound has refcount 1 initially' );
342
343   is( $handle->notifier_name, "", '$handle->notifier_name for late bind before handles' );
344
345   $loop->add( $handle );
346
347   is_refcount( $handle, 2, '$handle latebound has refcount 2 after $loop->add' );
348
349   my ( $S1, $S2 ) = mkhandles;
350   my $fd1 = $S1->fileno;
351
352   $handle->set_handle( $S1 );
353
354   is( $handle->read_handle,  $S1, '->read_handle now S1' );
355   is( $handle->write_handle, $S1, '->write_handle now S1' );
356
357   is_refcount( $handle, 2, '$handle latebound still has refcount 2 after set_handle' );
358
359   is( $handle->notifier_name, "rw=$fd1", '$handle->notifier_name for late bind after handles' );
360
361   $S2->syswrite( "readable" );
362
363   wait_for { $readready };
364   pass( '$handle latebound still invokes on_read_ready' );
365
366   $loop->remove( $handle );
367}
368
369# ->socket and ->bind
370{
371   my $handle = IO::Async::Handle->new( on_read_ready => sub {}, on_write_ready => sub {} );
372
373   $handle->socket( [ 'inet', 'stream', 0 ] );
374
375   ok( defined $handle->read_handle, '->socket sets handle' );
376
377   is( $handle->read_handle->sockdomain,       AF_INET,     'handle->sockdomain is AF_INET' );
378   is( $handle->read_handle->sockopt(SO_TYPE), SOCK_STREAM, 'handle->socktype is SOCK_STREAM' );
379
380   $handle->bind( { family => "inet", socktype => "dgram" } )->get;
381
382   is( $handle->read_handle->sockopt(SO_TYPE), SOCK_DGRAM, 'handle->socktype is SOCK_DGRAM' );
383   # Not sure what port number but it should be nonzero
384   ok( ( unpack_sockaddr_in( $handle->read_handle->sockname ) )[0], 'handle->sockname has nonzero port' );
385}
386
387# Construction of IO::Handle from fileno
388{
389   my $handle = IO::Async::Handle->new(
390      read_fileno => 0,
391      on_read_ready => sub { },
392   );
393
394   ok( defined $handle->read_handle, '->new with read_fileno creates read_handle' );
395   is( $handle->read_handle->fileno, 0, '->fileno of read_handle' );
396
397   $handle = IO::Async::Handle->new(
398      write_fileno => 1,
399      on_write_ready => sub { },
400   );
401
402   ok( defined $handle->write_handle, '->new with write_fileno creates write_handle' );
403   is( $handle->write_handle->fileno, 1, '->fileno of write_handle' );
404
405   $handle = IO::Async::Handle->new(
406      read_fileno  => 2,
407      write_fileno => 2,
408      on_read_ready  => sub { },
409      on_write_ready => sub { },
410   );
411
412   identical( $handle->read_handle, $handle->write_handle,
413      '->new with equal read and write fileno only creates one handle' );
414}
415
416done_testing;
417
418package TestHandle;
419use base qw( IO::Async::Handle );
420
421sub on_read_ready  { $sub_readready = 1 }
422sub on_write_ready { $sub_writeready = 1 }
423