File Coverage

blib/lib/IO/Async/Test.pm
Criterion Covered Total %
statement 36 40 90.0
branch 5 8 62.5
condition 3 5 60.0
subroutine 11 12 91.6
pod 4 4 100.0
total 59 69 85.5


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, 2007-2024 -- leonerd@leonerd.org.uk
5              
6             package IO::Async::Test 0.805;
7              
8 94     94   552943 use v5.14;
  94         383  
9 94     94   483 use warnings;
  94         519  
  94         5163  
10              
11 94     94   670 use Exporter 'import';
  94         157  
  94         65300  
12             our @EXPORT = qw(
13             testing_loop
14             wait_for
15             wait_for_stream
16             wait_for_future
17             );
18              
19             =head1 NAME
20              
21             C - utility functions for use in test scripts
22              
23             =head1 SYNOPSIS
24              
25             =for highlighter language=perl
26              
27             use Test2::V0;
28             use Future::AsyncAwait;
29             use IO::Async::Test;
30              
31             use IO::Async::Loop;
32             my $loop = IO::Async::Loop->new;
33             testing_loop( $loop );
34              
35             my $result;
36              
37             $loop->do_something(
38             some => args,
39              
40             on_done => sub {
41             $result = the_outcome;
42             }
43             );
44              
45             wait_for { defined $result };
46              
47             is( $result, what_we_expected, 'The event happened' );
48              
49             ...
50              
51             my $buffer = "";
52             my $handle = IO::Handle-> ...
53              
54             wait_for_stream { length $buffer >= 10 } $handle => $buffer;
55              
56             is( substr( $buffer, 0, 10, "" ), "0123456789", 'Buffer was correct' );
57              
58             my $result = await wait_for_future( $stream->read_until( "\n" ) );
59              
60             done_testing;
61              
62             =head1 DESCRIPTION
63              
64             This module provides utility functions that may be useful when writing test
65             scripts for code which uses L (as well as being used in the
66             L test scripts themselves).
67              
68             Test scripts are often synchronous by nature; they are a linear sequence of
69             actions to perform, interspersed with assertions which check for given
70             conditions. This goes against the very nature of L which, being an
71             asynchronisation framework, does not provide a linear stepped way of working.
72              
73             In order to write a test, the C function provides a way of
74             synchronising the code, so that a given condition is known to hold, which
75             would typically signify that some event has occurred, the outcome of which can
76             now be tested using the usual testing primitives.
77              
78             Because the primary purpose of L is to provide IO operations on
79             filehandles, a great many tests will likely be based around connected pipes or
80             socket handles. The C function provides a convenient way
81             to wait for some content to be written through such a connected stream.
82              
83             =cut
84              
85             my $loop;
86 66     66   46485 END { undef $loop }
87              
88             =head1 FUNCTIONS
89              
90             =cut
91              
92             =head2 testing_loop
93              
94             testing_loop( $loop );
95              
96             Set the L object which the C function will loop
97             on.
98              
99             =cut
100              
101             sub testing_loop
102             {
103 123     123 1 7534 $loop = shift;
104             }
105              
106             =head2 wait_for
107              
108             wait_for { COND } OPTS;
109              
110             Repeatedly call the C method on the underlying loop (given to the
111             C function), until the given condition function callback
112             returns true.
113              
114             To guard against stalled scripts, if the loop indicates a timeout for (a
115             default of) 10 consequentive seconds, then an error is thrown.
116              
117             Takes the following named options:
118              
119             =over 4
120              
121             =item timeout => NUM
122              
123             The time in seconds to wait before giving up the test as being stalled.
124             Defaults to 10 seconds.
125              
126             =back
127              
128             =cut
129              
130             our $Level = 0;
131              
132             sub wait_for(&@)
133             {
134 481     481 1 92347 my ( $cond, %opts ) = @_;
135              
136 481         14733 my ( undef, $callerfile, $callerline ) = caller( $Level );
137              
138 481         1348 my $timedout = 0;
139             my $timerid = $loop->watch_time(
140             after => $opts{timeout} // 10,
141 0     0   0 code => sub { $timedout = 1 },
142 481   50     21483 );
143              
144 481   66     1556 $loop->loop_once( 1 ) while !$cond->() and !$timedout;
145              
146 479 50       4740 if( $timedout ) {
147 0         0 die "Nothing was ready after 10 second wait; called at $callerfile line $callerline\n";
148             }
149             else {
150 479         4168 $loop->unwatch_time( $timerid );
151             }
152             }
153              
154             =head2 wait_for_stream
155              
156             wait_for_stream { COND } $handle, $buffer;
157              
158             As C, but will also watch the given IO handle for readability, and
159             whenever it is readable will read bytes in from it into the given buffer. The
160             buffer is NOT initialised when the function is entered, in case data remains
161             from a previous call.
162              
163             C<$buffer> can also be a CODE reference, in which case it will be invoked
164             being passed data read from the handle, whenever it is readable.
165              
166             =cut
167              
168             sub wait_for_stream(&$$)
169             {
170 7     7 1 770 my ( $cond, $handle, undef ) = @_;
171              
172 7         28 my $on_read;
173 7 100       59 if( ref $_[2] eq "CODE" ) {
174 1         2 $on_read = $_[2];
175             }
176             else {
177 6         17 my $varref = \$_[2];
178 6     6   69 $on_read = sub { $$varref .= $_[0] };
  6         23  
179             }
180              
181             $loop->watch_io(
182             handle => $handle,
183             on_read_ready => sub {
184 7     7   44 my $ret = $handle->sysread( my $buffer, 8192 );
185 7 50       267 if( !defined $ret ) {
    50          
186 0         0 die "Read failed on $handle - $!\n";
187             }
188             elsif( $ret == 0 ) {
189 0         0 die "Read returned EOF on $handle\n";
190             }
191 7         23 $on_read->( $buffer );
192             }
193 7         17865 );
194              
195 7         18 local $Level = $Level + 1;
196             # Have to defeat the prototype... grr I hate these
197 7         67 &wait_for( $cond );
198              
199 7         27 $loop->unwatch_io(
200             handle => $handle,
201             on_read_ready => 1,
202             );
203             }
204              
205             =head2 wait_for_future
206              
207             $future = wait_for_future $future;
208              
209             I
210              
211             A handy wrapper around using C to wait for a L to become
212             ready. The future instance itself is returned, allowing neater code.
213              
214             =cut
215              
216             sub wait_for_future
217             {
218 40     40 1 1402 my ( $future ) = @_;
219              
220 40         557 local $Level = $Level + 1;
221 40     115   1469 wait_for { $future->is_ready };
  115         747  
222              
223 40         376 return $future;
224             }
225              
226             =head1 AUTHOR
227              
228             Paul Evans
229              
230             =cut
231              
232             0x55AA;