File Coverage

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