File Coverage

blib/lib/Test/Future/IO.pm
Criterion Covered Total %
statement 64 72 88.8
branch 3 6 50.0
condition n/a
subroutine 22 24 91.6
pod 4 11 36.3
total 93 113 82.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, 2020-2024 -- leonerd@leonerd.org.uk
5              
6             package Test::Future::IO 0.06;
7              
8 6     6   1494792 use v5.14;
  6         23  
9 6     6   27 use warnings;
  6         9  
  6         315  
10              
11 6     6   26 use Carp;
  6         23  
  6         405  
12              
13 6     6   2843 use Test::ExpectAndCheck::Future 0.05; # ->whenever
  6         226814  
  6         349  
14 6     6   58 use Test::Deep ();
  6         21  
  6         4499  
15              
16             =head1 NAME
17              
18             C - unit testing on C
19              
20             =head1 SYNOPSIS
21              
22             use Test::More;
23             use Test::Future::IO;
24              
25             my $controller = Test::Future::IO->controller;
26              
27             {
28             $controller->expect_syswrite_anyfh( "Hello, world\n" );
29             $controller->expect_sysread_anyfh( 256 )
30             ->will_done( "A string\n" );
31              
32             code_under_test();
33              
34             $controller->check_and_clear( 'code under test did correct IO' );
35             }
36              
37             =head1 DESCRIPTION
38              
39             This package provides a means to apply unit testing around code which uses
40             L. It operates in an "expect-and-check" style of mocking,
41             requiring the test script to declare upfront what methods are expected to be
42             called, and what values they return.
43              
44             =cut
45              
46             =head1 EXPECTATIONS
47              
48             Each of the actual C methods has a corresponding expectation
49             method on the controller object, whose name is prefixed with C. A
50             single call to one of these methods by the unit test script represents a
51             single call to a C method that the code under test is expected to
52             make. The arguments to the expectation method should match those given by the
53             code under test. Each expectation method returns an object which has
54             additional methods to control the behaviour of that invocation.
55              
56             $exp = $controller->expect_accept( $fh );
57              
58             $exp = $controller->expect_connect( $fh, $name );
59              
60             $exp = $controller->expect_sleep( $secs );
61              
62             $exp = $controller->expect_sysread( $fh, $len );
63             $exp = $controller->expect_syswrite( $fh, $bytes );
64              
65             For testing simpler code that does not operate on multiple filehandles, two
66             additional methods that ignore the filehandle argument may be more convenient:
67              
68             $exp = $controller->expect_sysread_anyfh( $len );
69             $exp = $controller->expect_syswrite_anyfh( $bytes );
70              
71             In each case the returned expectation object allows the test script to specify
72             what such an invocation should return.
73              
74             $exp->will_done( @result );
75              
76             Expectations can make methods fail instead.
77              
78             $exp->will_fail( $message );
79             $exp->will_fail( $message, $category, @details );
80              
81             Expectations can be set to remain pending rather than completing.
82              
83             $exp->remains_pending;
84              
85             As a convenience, a C expectation will default to returning a future
86             that will complete yielding its length (as is usual for successful writes),
87             and a C or C expectation will return a future that completes
88             yielding nothing.
89              
90             Testing event-based code with C can be fragile, as it relies
91             on exact ordering, buffer sizes, and so on. A more flexible approach that
92             leads to less brittle tests is to use a buffer around that filehandle that is
93             provided by the test module. The test module then intercepts all C
94             method calls on the given filehandle to return data from that buffer:
95              
96             $controller->use_sysread_buffer( $fh );
97              
98             $controller->write_sysread_buffer( $fh, $data );
99              
100             As a convenience for filling the sysread buffer at the right time, any
101             expectation returned by this module supports two extra methods for invoking
102             C when another expectation completes:
103              
104             $exp->will_write_sysread_buffer( $fh, $data );
105              
106             $exp->will_write_sysread_buffer_later( $fh, $data );
107              
108             These are both shortcuts for calling L from within a
109             C or C code block.
110              
111             =cut
112              
113             my ( $controller, $obj ) = Test::Future::IO::_Controller->create;
114              
115             my %sysread_buffers;
116              
117             require Future::IO;
118             Future::IO->override_impl( $obj );
119              
120             sub expect_accept
121             {
122 3     3 0 22814 my $self = shift;
123 3         10 my ( $fh ) = @_;
124              
125 3         30 return $controller->expect( accept => $fh );
126             }
127              
128             sub expect_connect
129             {
130 3     3 0 23035 my $self = shift;
131 3         11 my ( $fh, $name ) = @_;
132              
133 3         30 return $controller->expect( connect => $fh, $name )
134             ->will_done();
135             }
136              
137             sub expect_sleep
138             {
139 4     4 0 22867 my $self = shift;
140 4         13 my ( $secs ) = @_;
141              
142 4         34 return $controller->expect( sleep => $secs )
143             ->will_done();
144             }
145              
146             sub expect_sysread
147             {
148 1     1 0 671 my $self = shift;
149 1         2 my ( $fh, $len ) = @_;
150 1 50       5 if( @_ == 1 ) {
151 0         0 carp "->expect_sysread with one argument is now deprecated";
152 0         0 ( $fh, $len ) = ( Test::Deep::ignore(), @_ );
153             }
154              
155 1         11 return $controller->expect( sysread => $fh, $len );
156             }
157              
158             sub expect_syswrite
159             {
160 1     1 0 1004 my $self = shift;
161 1         3 my ( $fh, $bytes ) = @_;
162 1 50       7 if( @_ == 1 ) {
163 0         0 carp "->expect_syswrite with one argument is now deprecated";
164 0         0 ( $fh, $bytes ) = ( Test::Deep::ignore(), @_ );
165             }
166              
167 1         15 return $controller->expect( syswrite => $fh, $bytes )
168             ->will_done( length $bytes );
169             }
170              
171             sub expect_sysread_anyfh
172             {
173 1     1 0 1296 my $self = shift;
174 1         5 $self->expect_sysread( Test::Deep::ignore() => @_ );
175             }
176              
177             sub expect_syswrite_anyfh
178             {
179 1     1 0 2652 my $self = shift;
180 1         8 $self->expect_syswrite( Test::Deep::ignore() => @_ );
181             }
182              
183             =head1 METHODS
184              
185             =cut
186              
187             =head2 controller
188              
189             $controller = Test::Future::IO->controller;
190              
191             Returns the control object, on which the various C methods and
192             C can be invoked.
193              
194             =cut
195              
196 5     5 1 1310178 sub controller { __PACKAGE__ }
197              
198             =head2 check_and_clear
199              
200             $controller->check_and_clear( $name );
201              
202             Checks that by now, every expected method has been called, and emits a new
203             test output line via L. Regardless, the expectations are also
204             cleared out ready for the start of the next test.
205              
206             =cut
207              
208             sub check_and_clear
209             {
210 16     16 1 94133 shift;
211 16         47 my ( $name ) = @_;
212              
213 16         39 local $Test::Builder::Level = $Test::Builder::Level + 1;
214 16         130 $controller->check_and_clear( $name );
215             }
216              
217             =head2 use_sysread_buffer
218              
219             $controller->use_sysread_buffer( $fh );
220              
221             I
222              
223             This method enables a read buffer for a given filehandle, that provides an
224             alternative means of testing reading on a filehandle than using
225             C. Once enabled, C<< Future::IO->sysread >> calls on the given
226             filehandle handled internally by the test controller.
227              
228             The sysread buffer is initially empty, and can be written to by
229             L.
230              
231             This is provided using a C C<< ->whenever >>
232             expectation, which is returned by this method. This is useful in case you want
233             to call the C<< ->indefinitely >> method on it, meaning it will survive past
234             calls to L.
235              
236             $controller->use_sysread_buffer( "FH" )
237             ->indefinitely;
238              
239             =cut
240              
241             sub use_sysread_buffer
242             {
243 2     2 1 10346 my $self = shift;
244 2         5 my ( $fh ) = @_;
245              
246 2         519 require Future::Buffer;
247              
248             # Not //= so that each test gets a new buffer
249 2         1826 my $buffer = $sysread_buffers{$fh} = Future::Buffer->new;
250              
251             return $controller->whenever( sysread => $fh, Test::Deep::ignore() )
252             ->will_return_using( sub {
253 2     2   1896 my ( $args ) = @_;
254 2         6 return $buffer->read_atmost( $args->[1] );
255 2         30 });
256             }
257              
258             =head2 write_sysread_buffer
259              
260             $controller->write_sysread_buffer( $fh, $data );
261              
262             I
263              
264             Appends more data to the sysread buffer previously established by the
265             L.
266              
267             Typically this is performed either initially as part of test setup, or later
268             as a side-effect of other expectations completing.
269              
270             For example:
271              
272             $controller->use_sysread_buffer( "FH" );
273              
274             $controller->expect_syswrite( "FH", "Question?\n" )
275             ->will_write_sysread_buffer_later( "FH", "Answer!\n" );
276              
277             =cut
278              
279             sub write_sysread_buffer
280             {
281 2     2 1 118 my $self = shift;
282 2         4 my ( $fh, $data ) = @_;
283              
284 2 50       9 my $buffer = $sysread_buffers{$fh} or
285             croak "Filehandle $fh is not managed by a Test::Future::IO buffer";
286              
287 2         5 $buffer->write( $data );
288             }
289              
290             {
291             package Test::Future::IO::_Controller;
292 6     6   45 use base qw( Test::ExpectAndCheck::Future );
  6         9  
  6         957  
293 6     6   40 use constant EXPECTATION_CLASS => "Test::Future::IO::_Expectation";
  6         12  
  6         547  
294             }
295              
296             {
297             package Test::Future::IO::_Expectation;
298 6     6   72 use base qw( Test::ExpectAndCheck::Future::_Expectation );
  6         15  
  6         3100  
299              
300             sub will_write_sysread_buffer
301             {
302 0     0   0 my $self = shift;
303 0         0 my ( $fh, $data ) = @_;
304              
305             return $self->will_also( sub {
306 0     0   0 Test::Future::IO->write_sysread_buffer( $fh, $data );
307 0         0 });
308             }
309              
310             sub will_write_sysread_buffer_later
311             {
312 1     1   50 my $self = shift;
313 1         2 my ( $fh, $data ) = @_;
314              
315             return $self->will_also_later( sub {
316 1     1   963 Test::Future::IO->write_sysread_buffer( $fh, $data );
317 1         10 });
318             }
319             }
320              
321             =head1 TODO
322              
323             =over 4
324              
325             =item *
326              
327             Provision of a mock filehandle object to assist unit tests.
328              
329             =back
330              
331             =head1 AUTHOR
332              
333             Paul Evans
334              
335             =cut
336              
337             0x55AA;