File Coverage

blib/lib/IO/Async/LoopTests.pm
Criterion Covered Total %
statement 425 458 92.7
branch 34 66 51.5
condition 1 3 33.3
subroutine 81 89 91.0
pod 1 11 9.0
total 542 627 86.4


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, 2009-2025 -- leonerd@leonerd.org.uk
5              
6             package IO::Async::LoopTests 0.805;
7              
8 24     24   4927121 use v5.14;
  24         129  
9 24     24   152 use warnings;
  24         197  
  24         1374  
10              
11 24     24   148 use Exporter 'import';
  24         38  
  24         1624  
12             our @EXPORT = qw(
13             run_tests
14             );
15              
16 24     24   132 use Test2::V0 0.000149;
  24         476  
  24         142  
17 24     24   43200 use Test::Metrics::Any;
  24         717551  
  24         296  
18              
19 24     24   64534 use IO::Async::Test qw();
  24         67  
  24         729  
20              
21 24     24   12607 use IO::Async::OS;
  24         80  
  24         1018  
22              
23 24     24   12023 use IO::File;
  24         231522  
  24         3521  
24 24     24   176 use Fcntl qw( SEEK_SET );
  24         36  
  24         1497  
25 24     24   134 use POSIX qw( SIGTERM );
  24         39  
  24         188  
26 24     24   1715 use Socket qw( sockaddr_family AF_UNIX );
  24         39  
  24         1242  
27 24     24   141 use Time::HiRes qw( time );
  24         41  
  24         218  
28              
29             # Abstract Units of Time
30 24 50   24   2434 use constant AUT => $ENV{TEST_QUICK_TIMERS} ? 0.1 : 1;
  24         44  
  24         134317  
31              
32             # The loop under test. We keep it in a single lexical here, so we can use
33             # is_oneref tests in the individual test suite functions
34             my $loop;
35 24     24   12710 END { undef $loop }
36              
37             =head1 NAME
38              
39             C - acceptance testing for L subclasses
40              
41             =head1 SYNOPSIS
42              
43             =for highlighter language=perl
44              
45             use IO::Async::LoopTests;
46             run_tests( 'IO::Async::Loop::Shiney', 'io' );
47              
48             =head1 DESCRIPTION
49              
50             This module contains a collection of test functions for running acceptance
51             tests on L subclasses. It is provided as a facility for
52             authors of such subclasses to ensure that the code conforms to the Loop API
53             required by L.
54              
55             =head1 TIMING
56              
57             Certain tests require the use of timers or timed delays. Normally these are
58             counted in units of seconds. By setting the environment variable
59             C to some true value, these timers run 10 times quicker,
60             being measured in units of 0.1 seconds instead. This value may be useful when
61             running the tests interactively, to avoid them taking too long. The slower
62             timers are preferred on automated smoke-testing machines, to help guard
63             against false negatives reported simply because of scheduling delays or high
64             system load while testing.
65              
66             $ TEST_QUICK_TIMERS=1 ./Build test
67              
68             =cut
69              
70             =head1 FUNCTIONS
71              
72             =cut
73              
74             =head2 run_tests
75              
76             run_tests( $class, @tests );
77              
78             Runs a test or collection of tests against the loop subclass given. The class
79             being tested is loaded by this function; the containing script does not need
80             to C or C it first.
81              
82             This function runs C to output its expected test count; the containing
83             script should not do this.
84              
85             =cut
86              
87             sub run_tests
88             {
89 24     24 1 7000179 my ( $testclass, @tests ) = @_;
90              
91 24         195 ( my $file = "$testclass.pm" ) =~ s{::}{/}g;
92              
93 24         64 eval { require $file };
  24         20333  
94 24 50       132 if( $@ ) {
95 0         0 bail_out( "Unable to load $testclass - $@" );
96             }
97              
98 24         97 foreach my $test ( @tests ) {
99 24         165 $loop = $testclass->new;
100              
101 24         236 isa_ok( $loop, [ $testclass ], '$loop' );
102              
103 24         10138 is( IO::Async::Loop->new, $loop, 'magic constructor yields $loop' );
104              
105             # Kill the reference in $ONE_TRUE_LOOP so as not to upset the refcounts
106             # and to ensure we get a new one each time
107 24         63388 undef $IO::Async::Loop::ONE_TRUE_LOOP;
108              
109 24         209 is_oneref( $loop, '$loop has refcount 1' );
110              
111 24         11151 __PACKAGE__->can( "run_tests_$test" )->();
112              
113 14         8790 is_oneref( $loop, '$loop has refcount 1 finally' );
114             }
115              
116 14         5328 done_testing;
117             }
118              
119             sub wait_for(&)
120             {
121             # Bounce via here so we don't upset refcount tests by having loop
122             # permanently set in IO::Async::Test
123 28     28 0 503 IO::Async::Test::testing_loop( $loop );
124              
125             # Override prototype - I know what I'm doing
126 28         125 &IO::Async::Test::wait_for( @_ );
127              
128 28         165 IO::Async::Test::testing_loop( undef );
129             }
130              
131             sub time_between(&$$$)
132             {
133 16     16 0 58 my ( $code, $lower, $upper, $name ) = @_;
134              
135             Test2::API::context_do {
136 16     16   2553 my $ctx = shift;;
137              
138 16         61 my $start = time;
139 16         51 $code->();
140 16         89 my $took = ( time - $start ) / AUT;
141              
142 16 100       240 $ctx->ok( $took >= $lower, "$name took at least $lower seconds" ) if defined $lower;
143 16 50       4362 $ctx->ok( $took <= $upper * 3, "$name took no more than $upper seconds" ) if defined $upper;
144 16 50 33     2452 if( $took > $upper and $took <= $upper * 3 ) {
145 0         0 $ctx->note( "$name took longer than $upper seconds - this may just be an indication of a busy testing machine rather than a bug" );
146             }
147 16         156 };
148             }
149              
150             =head1 TEST SUITES
151              
152             The following test suite names exist, to be passed as a name in the C<@tests>
153             argument to C:
154              
155             =cut
156              
157             =head2 io
158              
159             Tests the Loop's ability to watch filehandles for IO readiness
160              
161             =cut
162              
163             sub run_tests_io
164             {
165             {
166 2 50       19 my ( $S1, $S2 ) = IO::Async::OS->socketpair or die "Cannot create socket pair - $!";
167 2         10 $_->blocking( 0 ) for $S1, $S2;
168              
169 2         36 my $readready = 0;
170 2         3 my $writeready = 0;
171             $loop->watch_io(
172             handle => $S1,
173 2     2   7 on_read_ready => sub { $readready = 1 },
174 2         19 );
175              
176 2         9 is_oneref( $loop, '$loop has refcount 1 after watch_io on_read_ready' );
177 2         691 is( $readready, 0, '$readready still 0 before ->loop_once' );
178              
179 2         663 $loop->loop_once( 0.1 );
180              
181 2         19 is( $readready, 0, '$readready when idle' );
182              
183 2         1922 $S2->syswrite( "data\n" );
184              
185             # We should still wait a little while even thought we expect to be ready
186             # immediately, because talking to ourself with 0 poll timeout is a race
187             # condition - we can still race with the kernel.
188              
189 2         105 $loop->loop_once( 0.1 );
190              
191 2         12 is( $readready, 1, '$readready after loop_once' );
192              
193             # Ready $S1 to clear the data
194 2         4729 $S1->getline; # ignore return
195              
196 2         20 $loop->unwatch_io(
197             handle => $S1,
198             on_read_ready => 1,
199             );
200              
201             $loop->watch_io(
202             handle => $S1,
203 4     4   9 on_read_ready => sub { $readready = 1 },
204 2         23 );
205              
206 2         4 $readready = 0;
207 2         11 $S2->syswrite( "more data\n" );
208              
209 2         58 $loop->loop_once( 0.1 );
210              
211 2         11 is( $readready, 1, '$readready after ->unwatch_io/->watch_io' );
212              
213 2         1269 $S1->getline; # ignore return
214              
215             $loop->watch_io(
216             handle => $S1,
217 2     2   7 on_write_ready => sub { $writeready = 1 },
218 2         19 );
219              
220 2         20 is_oneref( $loop, '$loop has refcount 1 after watch_io on_write_ready' );
221              
222 2         794 $loop->loop_once( 0.1 );
223              
224 2         10 is( $writeready, 1, '$writeready after loop_once' );
225              
226 2         933 $loop->unwatch_io(
227             handle => $S1,
228             on_write_ready => 1,
229             );
230              
231 2         6 $readready = 0;
232 2         8 $loop->loop_once( 0.1 );
233              
234 2         24 is( $readready, 0, '$readready before HUP' );
235              
236 2         1830 $S2->close;
237              
238 2         189 $readready = 0;
239 2         13 $loop->loop_once( 0.1 );
240              
241 2         10 is( $readready, 1, '$readready after HUP' );
242              
243 2         1136 $loop->unwatch_io(
244             handle => $S1,
245             on_read_ready => 1,
246             );
247             }
248              
249             # Check that read- and write-readiness can be watched via two separate
250             # ->watch_io calls on the same handle
251             {
252 2 50   2 0 6 my ( $S1, $S2 ) = IO::Async::OS->socketpair or die "Cannot create socket pair - $!";
  2         25  
253 2         14 $_->blocking( 0 ) for $S1, $S2;
254              
255 2         50 foreach my $first (qw( read write )) {
256 4         9 my $readready = 0;
257 4         6 my $writeready = 0;
258              
259             $loop->watch_io(
260             handle => $S1,
261 4     4   10 on_read_ready => sub { $readready = 1 },
262 4 100       30 ) if $first eq "read";
263             $loop->watch_io(
264             handle => $S1,
265 8     8   18 on_write_ready => sub { $writeready = 1 },
266 4         38 );
267             $loop->watch_io(
268             handle => $S1,
269 4     4   9 on_read_ready => sub { $readready = 1 },
270 4 100       98 ) if $first eq "write";
271              
272 4         24 $S2->syswrite( "data\n" );
273              
274 4         137 $loop->loop_once( 0.1 ) for 1..2;
275              
276 4         29 ok( $readready, '$readready after '.$first.' first split read+write watch' );
277 4         2919 ok( $writeready, '$writeready after '.$first.' first split read+write watch' );
278              
279 4         1122 $loop->unwatch_io(
280             handle => $S1,
281             on_read_ready => 1,
282             on_write_ready => 1,
283             );
284             }
285             }
286              
287             # HUP of pipe - can be different to sockets on some architectures
288             {
289 2 50       13 my ( $Prd, $Pwr ) = IO::Async::OS->pipepair or die "Cannot pipepair - $!";
  2         12  
  2         43  
290 2         60 $_->blocking( 0 ) for $Prd, $Pwr;
291              
292 2         5 my $readready = 0;
293             $loop->watch_io(
294             handle => $Prd,
295 2     2   5 on_read_ready => sub { $readready = 1 },
296 2         18 );
297              
298 2         9 $loop->loop_once( 0.1 );
299              
300 2         23 is( $readready, 0, '$readready before pipe HUP' );
301              
302 2         1655 $Pwr->close;
303              
304 2         49 $readready = 0;
305 2         15 $loop->loop_once( 0.1 );
306              
307 2         50 is( $readready, 1, '$readready after pipe HUP' );
308              
309 2         1029 $loop->unwatch_io(
310             handle => $Prd,
311             on_read_ready => 1,
312             );
313             }
314              
315             SKIP: {
316 2 100       84 $loop->_CAN_ON_HANGUP or skip "Loop cannot watch_io for on_hangup", 2;
317              
318             SKIP: {
319 1 50       6 my ( $S1, $S2 ) = IO::Async::OS->socketpair or die "Cannot socketpair - $!";
  1         13  
320 1         9 $_->blocking( 0 ) for $S1, $S2;
321              
322 1 50       21 sockaddr_family( $S1->sockname ) == AF_UNIX or skip "Cannot reliably detect hangup condition on non AF_UNIX sockets", 1;
323              
324 1         18 my $hangup = 0;
325             $loop->watch_io(
326             handle => $S1,
327 1     1   3 on_hangup => sub { $hangup = 1 },
328 1         13 );
329              
330 1         4 $S2->close;
331              
332 1         29 $loop->loop_once( 0.1 );
333              
334 1         4113 is( $hangup, 1, '$hangup after socket close' );
335              
336 1         767 $loop->unwatch_io(
337             handle => $S1,
338             on_hangup => 1,
339             );
340             }
341              
342 1 50       22 my ( $Prd, $Pwr ) = IO::Async::OS->pipepair or die "Cannot pipepair - $!";
343 1         21 $_->blocking( 0 ) for $Prd, $Pwr;
344              
345 1         4 my $hangup = 0;
346             $loop->watch_io(
347             handle => $Pwr,
348 1     1   3 on_hangup => sub { $hangup = 1 },
349 1         16 );
350              
351 1         6 $Prd->close;
352              
353 1         17 $loop->loop_once( 0.1 );
354              
355 1         4 is( $hangup, 1, '$hangup after pipe close for writing' );
356              
357 1         492 $loop->unwatch_io(
358             handle => $Pwr,
359             on_hangup => 1,
360             );
361             }
362              
363             # Check that combined read/write handlers can cancel each other
364             {
365 2 50       6 my ( $S1, $S2 ) = IO::Async::OS->socketpair or die "Cannot socketpair - $!";
  2         20  
366 2         14 $_->blocking( 0 ) for $S1, $S2;
367              
368 2         46 my $callcount = 0;
369             $loop->watch_io(
370             handle => $S1,
371             on_read_ready => sub {
372 2     2   6 $callcount++;
373 2         8 $loop->unwatch_io( handle => $S1, on_read_ready => 1, on_write_ready => 1 );
374             },
375             on_write_ready => sub {
376 0     0   0 $callcount++;
377 0         0 $loop->unwatch_io( handle => $S1, on_read_ready => 1, on_write_ready => 1 );
378             },
379 2         59 );
380              
381 2         12 $S2->close;
382              
383 2         66 $loop->loop_once( 0.1 );
384              
385 2         9 is( $callcount, 1, 'read/write_ready can cancel each other' );
386             }
387              
388             # Check that cross-connected handlers can cancel each other
389             {
390 2 50       1377 my ( $SA1, $SA2 ) = IO::Async::OS->socketpair or die "Cannot socketpair - $!";
  2         1151  
  2         21  
391 2 50       10141 my ( $SB1, $SB2 ) = IO::Async::OS->socketpair or die "Cannot socketpair - $!";
392 2         48 $_->blocking( 0 ) for $SA1, $SA2, $SB1, $SB2;
393              
394 2         103 my @handles = ( $SA1, $SB1 );
395              
396 2         4 my $callcount = 0;
397             $loop->watch_io(
398             handle => $_,
399             on_write_ready => sub {
400 2     2   6 $callcount++;
401 2         15 $loop->unwatch_io( handle => $_, on_write_ready => 1 ) for @handles;
402             },
403 2         34 ) for @handles;
404              
405 2         12 $loop->loop_once( 0.1 );
406              
407 2         14 is( $callcount, 1, 'write_ready on crosslinked handles can cancel each other' );
408             }
409              
410             # Check that error conditions that aren't true read/write-ability are still
411             # invoked
412             SKIP: {
413 2 50       13 skip "cygwin does not indicate read-ready on exceptional sockets", 1 if $^O eq "cygwin";
414              
415 2 50       16 my ( $S1, $S2 ) = IO::Async::OS->socketpair( 'inet', 'dgram' ) or die "Cannot create AF_INET/SOCK_DGRAM connected pair - $!";
416 2         32 $_->blocking( 0 ) for $S1, $S2;
417 2         74 $S2->close;
418              
419 2         89 my $readready = 0;
420             $loop->watch_io(
421             handle => $S1,
422 2     2   8 on_read_ready => sub { $readready = 1 },
423 2         29 );
424              
425 2         45 $S1->syswrite( "Boo!" );
426              
427 2         295 $loop->loop_once( 0.1 );
428              
429 2         15 is( $readready, 1, 'exceptional socket invokes on_read_ready' );
430              
431 2         3042 $loop->unwatch_io(
432             handle => $S1,
433             on_read_ready => 1,
434             );
435             }
436              
437             # Check that regular files still report read/writereadiness
438             {
439 2 50       1535 my $F = IO::File->new_tmpfile or die "Cannot create temporary file - $!";
  2         978  
440              
441 2         27 $F->print( "Here's some content\n" );
442 2         163 $F->seek( 0, SEEK_SET );
443              
444 2         142 my $readready = 0;
445 2         7 my $writeready = 0;
446             $loop->watch_io(
447             handle => $F,
448 2     2   6 on_read_ready => sub { $readready = 1 },
449 2     2   5 on_write_ready => sub { $writeready = 1 },
450 2         26 );
451              
452 2         12 $loop->loop_once( 0.1 );
453              
454 2         14 is( $readready, 1, 'regular file is readready' );
455 2         2596 is( $writeready, 1, 'regular file is writeready' );
456              
457 2         933 $loop->unwatch_io(
458             handle => $F,
459             on_read_ready => 1,
460             on_write_ready => 1,
461             );
462             }
463              
464             {
465 2 50       15 my ( $S1, $S2 ) = IO::Async::OS->socketpair or die "Cannot create AF_INET/SOCK_DGRAM connected pair - $!";
  2         9  
  2         21  
466 2         17 $_->blocking( 0 ) for $S1, $S2;
467              
468 2         65 my $readready = 0;
469             $loop->watch_io(
470             handle => $S1,
471 2     2   8 on_read_ready => sub { $readready = 1 },
472 2         23 );
473              
474 2         30 $loop->post_fork;
475              
476 2         11 $S2->syswrite( "Boo!" );
477              
478 2         73 $loop->loop_once( 0.1 );
479              
480 2         9 is( $readready, 1, 'socket invokes on_read_ready' );
481             }
482             }
483              
484             =head2 timer
485              
486             Tests the Loop's ability to handle timer events
487              
488             =cut
489              
490             sub run_tests_timer
491             {
492             # New watch/unwatch API
493              
494 2     2 0 18 cmp_ok( abs( $loop->time - time ), "<", 0.1, '$loop->time gives the current time' );
495              
496             # ->watch_time after
497             {
498 2         5 my $done;
499 2     2   32 $loop->watch_time( after => 2 * AUT, code => sub { $done = 1; } );
  2         7  
500              
501 2         9 is_oneref( $loop, '$loop has refcount 1 after watch_time' );
502              
503             time_between {
504 2     2   5 my $now = time;
505 2         9 $loop->loop_once( 5 * AUT );
506              
507             # poll might have returned just a little early, such that the TimerQueue
508             # doesn't think anything is ready yet. We need to handle that case.
509 2         11 while( !$done ) {
510 0 0       0 die "It should have been ready by now" if( time - $now > 5 * AUT );
511 0         0 $loop->loop_once( 0.1 * AUT );
512             }
513 2         853 } 1.5, 2.5, 'loop_once(5) while waiting for watch_time after';
514             }
515              
516             # ->watch_time at
517             {
518 2         483 my $done;
  2         6  
519 2     2   29 $loop->watch_time( at => time + 2 * AUT, code => sub { $done = 1; } );
  2         11  
520              
521             time_between {
522 2     2   9 my $now = time;
523 2         12 $loop->loop_once( 5 * AUT );
524              
525             # poll might have returned just a little early, such that the TimerQueue
526             # doesn't think anything is ready yet. We need to handle that case.
527 2         21 while( !$done ) {
528 0 0       0 die "It should have been ready by now" if( time - $now > 5 * AUT );
529 0         0 $loop->loop_once( 0.1 * AUT );
530             }
531 2         19 } 1.5, 2.5, 'loop_once(5) while waiting for watch_time at';
532             }
533              
534             # cancelled timer
535             {
536 2         207 my $cancelled_fired = 0;
  2         8  
537 2     0   26 my $id = $loop->watch_time( after => 1 * AUT, code => sub { $cancelled_fired = 1 } );
  0         0  
538 2         32 $loop->unwatch_time( $id );
539 2         10 undef $id;
540              
541 2         12 $loop->loop_once( 2 * AUT );
542              
543 2         20 ok( !$cancelled_fired, 'unwatched watch_time does not fire' );
544             }
545              
546             # ->watch_after negative time
547             {
548 2         276 my $done;
  2         4  
549 2     2   21 $loop->watch_time( after => -1, code => sub { $done = 1 } );
  2         5  
550              
551             time_between {
552 2     2   15 $loop->loop_once while !$done;
553 2         12 } 0, 0.1, 'loop_once while waiting for negative interval timer';
554             }
555              
556             # self-cancellation
557             {
558 2         947 my $done;
  2         157  
  2         6  
559              
560             my $id;
561             $id = $loop->watch_time( after => 1 * AUT, code => sub {
562 2     2   53 $loop->unwatch_time( $id ); undef $id;
  2         8  
563 2         18 });
564              
565             $loop->watch_time( after => 1.1 * AUT, code => sub {
566 2     2   16 $done++;
567 2         14 });
568              
569 2     6   13 wait_for { $done };
  6         96  
570              
571 2         27 is( $done, 1, 'Other timers still fire after self-cancelling one' );
572             }
573              
574             SKIP: {
575 2 50       89 skip "Unable to handle sub-second timers accurately", 3 unless $loop->_CAN_SUBSECOND_ACCURATELY;
576              
577             # Check that short delays are achievable in one ->loop_once call
578 0         0 foreach my $delay ( 0.001, 0.01, 0.1 ) {
579 0         0 my $done;
580 0         0 my $count = 0;
581 0         0 my $start = time;
582              
583 0     0   0 $loop->watch_timer( delay => $delay, code => sub { $done++ } );
  0         0  
584              
585 0         0 while( !$done ) {
586 0         0 $loop->loop_once( 1 );
587 0         0 $count++;
588 0 0       0 last if time - $start > 5; # bailout
589             }
590              
591 0         0 is( $count, 1, "One ->loop_once(1) sufficient for a single $delay second timer" );
592             }
593             }
594              
595             # ->watch_time after post_fork
596             {
597 2         2278 my $done;
  2         3994  
  2         5  
598 2     2   64 $loop->watch_time( after => 2 * AUT, code => sub { $done = 1; } );
  2         7  
599 2         21 $loop->post_fork;
600              
601             time_between {
602 2     2   8 my $now = time;
603 2         12 $loop->loop_once( 5 * AUT );
604              
605             # poll might have returned just a little early, such that the TimerQueue
606             # doesn't think anything is ready yet. We need to handle that case.
607 2         10 while( !$done ) {
608 0 0       0 die "It should have been ready by now" if( time - $now > 5 * AUT );
609 0         0 $loop->loop_once( 0.1 * AUT );
610             }
611 2         20 } 1.5, 2.5, 'loop_once(5) while waiting for watch_time after';
612             }
613             }
614              
615             =head2 signal
616              
617             Tests the Loop's ability to watch POSIX signals
618              
619             =cut
620              
621             sub run_tests_signal
622             {
623 2 50   2 0 16 unless( IO::Async::OS->HAVE_SIGNALS ) {
624 0         0 SKIP: { skip "This OS does not have signals", 14; }
  0         0  
625 0         0 return;
626             }
627              
628 2         6 my $caught = 0;
629              
630 2     4   69 $loop->watch_signal( TERM => sub { $caught++ } );
  4         18  
631              
632 2         38 is_oneref( $loop, '$loop has refcount 1 after watch_signal' );
633              
634 2         1221 $loop->loop_once( 0.1 );
635              
636 2         22 is( $caught, 0, '$caught idling' );
637              
638 2         1919 kill SIGTERM, $$;
639              
640 2         14 is( $caught, 0, '$caught before ->loop_once' );
641              
642 2         1063 $loop->loop_once( 0.1 );
643              
644 2         11 is( $caught, 1, '$caught after ->loop_once' );
645              
646 2         1180 kill SIGTERM, $$;
647              
648 2         13 is( $caught, 1, 'second raise is still deferred' );
649              
650 2         1022 $loop->loop_once( 0.1 );
651              
652 2         14 is( $caught, 2, '$caught after second ->loop_once' );
653              
654 2         3477 is_oneref( $loop, '$loop has refcount 1 before unwatch_signal' );
655              
656 2         1344 $loop->unwatch_signal( 'TERM' );
657              
658 2         10 is_oneref( $loop, '$loop has refcount 1 after unwatch_signal' );
659              
660 2         1310 my ( $cA, $cB );
661              
662 2     2   37 my $idA = $loop->attach_signal( TERM => sub { $cA = 1 } );
  2         8  
663 2     4   15 my $idB = $loop->attach_signal( TERM => sub { $cB = 1 } );
  4         20  
664              
665 2         13 is_oneref( $loop, '$loop has refcount 1 after 2 * attach_signal' );
666              
667 2         955 kill SIGTERM, $$;
668              
669 2         16 $loop->loop_once( 0.1 );
670              
671 2         12 is( $cA, 1, '$cA after raise' );
672 2         5251 is( $cB, 1, '$cB after raise' );
673              
674 2         2119 $loop->detach_signal( 'TERM', $idA );
675              
676 2         5 undef $cA;
677 2         5 undef $cB;
678              
679 2         74 kill SIGTERM, $$;
680              
681 2         122 $loop->loop_once( 0.1 );
682              
683 2         13 is( $cA, undef, '$cA after raise' );
684 2         1495 is( $cB, 1, '$cB after raise' );
685              
686 2         1076 $loop->detach_signal( 'TERM', $idB );
687              
688 2     2   45 ok( dies { $loop->attach_signal( 'this signal name does not exist', sub {} ) },
689 2         25 'Bad signal name fails' );
690              
691 2         774 undef $caught;
692 2     2   18 $loop->attach_signal( TERM => sub { $caught++ } );
  2         34  
693              
694 2         34 $loop->post_fork;
695              
696 2         122 kill SIGTERM, $$;
697              
698 2         28 $loop->loop_once( 0.1 );
699              
700 2         13 is( $caught, 1, '$caught SIGTERM after ->post_fork' );
701             }
702              
703             =head2 idle
704              
705             Tests the Loop's support for idle handlers
706              
707             =cut
708              
709             sub run_tests_idle
710             {
711 2     2 0 4 my $called = 0;
712              
713 2     2   125 my $id = $loop->watch_idle( when => 'later', code => sub { $called = 1 } );
  2         5  
714              
715 2         15 ok( defined $id, 'idle watcher id is defined' );
716              
717 2         644 is( $called, 0, 'deferred sub not yet invoked' );
718              
719 2     2   947 time_between { $loop->loop_once( 3 * AUT ) } undef, 1.0, 'loop_once(3) with deferred sub';
  2         25  
720              
721 2         141 is( $called, 1, 'deferred sub called after loop_once' );
722              
723             $loop->watch_idle( when => 'later', code => sub {
724 2         7 $loop->watch_idle( when => 'later', code => sub { $called = 2 } )
725 2     2   2326 } );
  2         18  
726              
727 2         12 $loop->loop_once( 1 );
728              
729 2         11 is( $called, 1, 'inner deferral not yet invoked' );
730              
731 2         2386 $loop->loop_once( 1 );
732              
733 2         12 is( $called, 2, 'inner deferral now invoked' );
734              
735 2         1240 $called = 2; # set it anyway in case previous test fails
736              
737 2     0   21 $id = $loop->watch_idle( when => 'later', code => sub { $called = 20 } );
  0         0  
738              
739 2         23 $loop->unwatch_idle( $id );
740              
741             # Some loop types (e.g. UV) need to clear a pending queue first and thus the
742             # first loop_once will take zero time
743 2         9 $loop->loop_once( 0 );
744              
745 2     2   37 time_between { $loop->loop_once( 1 * AUT ) } 0.5, 1.5, 'loop_once(1) with unwatched deferral';
  2         9  
746              
747 2         204 is( $called, 2, 'unwatched deferral not called' );
748              
749 2     2   1816 $id = $loop->watch_idle( when => 'later', code => sub { $called = 3 } );
  2         6  
750 2     0   28 my $timer_id = $loop->watch_time( after => 5, code => sub {} );
751              
752 2         13 $loop->loop_once( 1 );
753              
754 2         10 is( $called, 3, '$loop->later still invoked with enqueued timer' );
755              
756 2         1347 $loop->unwatch_time( $timer_id );
757              
758 2     2   23 $loop->later( sub { $called = 4 } );
  2         19  
759              
760 2         10 $loop->loop_once( 1 );
761              
762 2         9 is( $called, 4, '$loop->later shortcut works' );
763             }
764              
765             =head2 process
766              
767             Tests the Loop's support for watching child processes by PID
768              
769             (Previously called C)
770              
771             =cut
772              
773             sub run_in_child(&)
774             {
775 50     50 0 119278 my $kid = fork;
776 50 50       3420 defined $kid or die "Cannot fork() - $!";
777 50 100       7061 return $kid if $kid;
778              
779 10         1131 shift->();
780 0         0 die "Fell out of run_in_child!\n";
781             }
782              
783             sub run_tests_process
784             {
785             my $kid = run_in_child {
786 2     2   965 exit( 3 );
787 12     12 0 198 };
788              
789 10         1080 my $exitcode;
790              
791 10     10   1400 $loop->watch_process( $kid => sub { ( undef, $exitcode ) = @_; } );
  10         40  
792              
793 10         190 is_oneref( $loop, '$loop has refcount 1 after watch_process' );
794 10         13785 ok( !defined $exitcode, '$exitcode not defined before ->loop_once' );
795              
796 10         3285 undef $exitcode;
797 10     35   225 wait_for { defined $exitcode };
  35         440  
798              
799 10         125 ok( ($exitcode & 0x7f) == 0, 'WIFEXITED($exitcode) after child exit' );
800 10         5000 is( ($exitcode >> 8), 3, 'WEXITSTATUS($exitcode) after child exit' );
801              
802             SKIP: {
803 10 50       5785 skip "This OS does not have signals", 1 unless IO::Async::OS->HAVE_SIGNALS;
  10         110  
804              
805             # We require that SIGTERM perform its default action; i.e. terminate the
806             # process. Ensure this definitely happens, in case the test harness has it
807             # ignored or handled elsewhere.
808 10         200 local $SIG{TERM} = "DEFAULT";
809              
810             $kid = run_in_child {
811 0     0   0 sleep( 10 );
812             # Just in case the parent died already and didn't kill us
813 0         0 exit( 0 );
814 10         90 };
815              
816 10     10   2600 $loop->watch_process( $kid => sub { ( undef, $exitcode ) = @_; } );
  10         50  
817              
818 10         675 kill SIGTERM, $kid;
819              
820 10         160 undef $exitcode;
821 10     25   490 wait_for { defined $exitcode };
  25         560  
822              
823 10         205 is( ($exitcode & 0x7f), SIGTERM, 'WTERMSIG($exitcode) after SIGTERM' );
824             }
825              
826             SKIP: {
827 10         8595 my %kids;
  10         25  
828              
829 10 50       530 $loop->_CAN_WATCH_ALL_PIDS or skip "Loop cannot watch_process for all PIDs", 2;
830              
831 10     14   165 $loop->watch_process( 0 => sub { my ( $kid ) = @_; delete $kids{$kid} } );
  14         49  
  14         79  
832              
833 10     6   30 %kids = map { run_in_child { exit 0 } => 1 } 1 .. 3;
  24         456  
  6         3697  
834              
835 4         746 is( scalar keys %kids, 3, 'Waiting for 3 child processes' );
836              
837 4     30   18176 wait_for { !keys %kids };
  30         466  
838 4         112 ok( !keys %kids, 'All child processes reclaimed' );
839             }
840              
841             # Legacy API name
842 4     2   2588 $kid = run_in_child { exit 2 };
  2         1098  
843              
844 2         375 undef $exitcode;
845 2     2   340 $loop->watch_child( $kid => sub { ( undef, $exitcode ) = @_; } );
  2         26  
846 2     9   5248 wait_for { defined $exitcode };
  9         160  
847              
848 2         63 is( ($exitcode >> 8), 2, '$exitcode after child exit from legacy ->watch_child' );
849             }
850             *run_tests_child = \&run_tests_process; # old name
851              
852             =head2 control
853              
854             Tests that the C, C, C and C methods
855             behave correctly
856              
857             =cut
858              
859             sub run_tests_control
860             {
861 2     2 0 10 time_between { $loop->loop_once( 0 ) } 0, 0.1, 'loop_once(0) when idle';
  2     2   23  
862              
863 2     2   135 time_between { $loop->loop_once( 2 * AUT ) } 1.5, 2.5, 'loop_once(2) when idle';
  2         10  
864              
865 2     2   225 $loop->watch_time( after => 0.1, code => sub { $loop->stop( result => "here" ) } );
  2         49  
866              
867 2     0   35 local $SIG{ALRM} = sub { die "Test timed out before ->stop" };
  0         0  
868 2         14 alarm( 1 );
869              
870 2         15 my @result = $loop->run;
871              
872 2         19 alarm( 0 );
873              
874 2         25 is( \@result, [ result => "here" ], '->stop arguments returned by ->run' );
875              
876 2     2   2341 $loop->watch_time( after => 0.1, code => sub { $loop->stop( result => "here" ) } );
  2         16  
877              
878 2         11 my $result = $loop->run;
879              
880 2         18 is( $result, "result", 'First ->stop argument returned by ->run in scalar context' );
881              
882             $loop->watch_time( after => 0.1, code => sub {
883             SKIP: {
884 2 50   2   6 unless( $loop->can( 'is_running' ) ) {
  2         39  
885 0         0 diag "Unsupported \$loop->is_running";
886 0         0 skip "Unsupported \$loop->is_running", 1;
887             }
888              
889 2         12 ok( $loop->is_running, '$loop->is_running' );
890             }
891              
892 2         859 $loop->watch_time( after => 0.1, code => sub { $loop->stop( "inner" ) } );
  2         12  
893 2         11 my @result = $loop->run;
894 2         9 $loop->stop( @result, "outer" );
895 2         1510 } );
896              
897 2         11 @result = $loop->run;
898              
899 2         20 is( \@result, [ "inner", "outer" ], '->run can be nested properly' );
900              
901 2     2   1870 $loop->watch_time( after => 0.1, code => sub { $loop->loop_stop } );
  2         68  
902              
903 2     0   32 local $SIG{ALRM} = sub { die "Test timed out before ->loop_stop" };
  0         0  
904 2         15 alarm( 1 );
905              
906 2         22 $loop->loop_forever;
907              
908 2         66 alarm( 0 );
909              
910 2         21 ok( 1, '$loop->loop_forever interruptable by ->loop_stop' );
911             }
912              
913             =head2 metrics
914              
915             Tests that metrics are generated appropriately using L.
916              
917             =cut
918              
919             sub run_tests_metrics
920             {
921 2     2 0 8 my $loopclass = ref $loop;
922              
923 2 50       10 return unless $IO::Async::Metrics::METRICS;
924              
925             # We should already at least have the loop-type metric
926 2         40 is_metrics(
927             {
928             "io_async_loops class:$loopclass" => 1,
929             },
930             'Constructing the loop creates a loop type metric'
931             );
932              
933             # The very first call won't create timing metrics because it isn't armed yet.
934 2         1318 $loop->loop_once( 0 );
935              
936             is_metrics_from(
937 2     2   124 sub { $loop->loop_once( 0.1 ) },
938             {
939 2         37 io_async_processing_count => 1,
940             io_async_processing_total => Test::Metrics::Any::positive,
941             },
942             'loop_once(0) creates timing metrics'
943             );
944             }
945              
946             =head1 AUTHOR
947              
948             Paul Evans
949              
950             =cut
951              
952             0x55AA;