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