File Coverage

blib/lib/Test/Device/Chip/Adapter.pm
Criterion Covered Total %
statement 100 139 71.9
branch 9 12 75.0
condition 3 9 33.3
subroutine 22 30 73.3
pod 1 10 10.0
total 135 200 67.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, 2015-2023 -- leonerd@leonerd.org.uk
5              
6 6     6   1547043 use v5.26;
  6         27  
7 6     6   45 use warnings;
  6         26  
  6         439  
8 6     6   4371 use Object::Pad 0.800;
  6         83912  
  6         601  
9              
10             package Test::Device::Chip::Adapter 0.26;
11             class Test::Device::Chip::Adapter
12 6     6   5300 :does(Device::Chip::Adapter);
  6         21  
  6         1083  
13              
14 6     6   737 use Carp;
  6         12  
  6         535  
15              
16 6     6   41 use Future::AsyncAwait;
  6         13  
  6         28  
17              
18 6     6   3725 use Test::Future::Deferred;
  6         5225  
  6         414  
19 6     6   51 use List::Util 1.33 qw( first any );
  6         130  
  6         619  
20 6     6   1763 use Test::Builder;
  6         82894  
  6         245  
21              
22 6     6   3993 use Test::ExpectAndCheck::Future;
  6         180919  
  6         14148  
23              
24             =encoding UTF-8
25              
26             =head1 NAME
27              
28             C - unit testing on C
29              
30             =head1 SYNOPSIS
31              
32             use Test::More;
33             use Test::Device::Chip::Adapter;
34              
35             use Future::AsyncAwait;
36              
37             my $adapter = Test::Device::Chip::Adapter->new;
38              
39             $chip_under_test->mount( $adapter );
40              
41             # An actual test
42             $adapter->expect_readwrite( "123" )
43             ->will_done( "45" );
44              
45             is( await $chip->do_thing( "123" ), "45", 'result of ->do_thing' );
46              
47             $adapter->check_and_clear( '->do_thing' );
48              
49             =head1 DESCRIPTION
50              
51             This package provides a concrete implementation of L
52             convenient for using in a unit-test script used to test a L
53             instance. It operates in an "expect-and-check" style of mocking, requiring the
54             test script to declare upfront what methods are expected to be called, and
55             what values they return.
56              
57             Futures returned by this module will not yield results immediately; they must
58             be awaited by a toplevel C keyword or invoking the C<< ->get >> method.
59             This ensures that unit tests correctly perform the required asynchronisation.
60              
61             =cut
62              
63             field $_protocol;
64              
65             field $_controller;
66             field $_obj;
67             field $_txn_helper;
68              
69             ADJUST
70             {
71             ( $_controller, $_obj ) = Test::Device::Chip::Adapter::_TestController->create;
72             }
73              
74 1     1 0 3 method make_protocol_GPIO ()
  1         3  
  1         2  
75             {
76 1         4 $_protocol = "GPIO";
77 1         9 return Test::Future::Deferred->done_later( $self );
78             }
79              
80 3     3 0 6 method make_protocol_I2C ()
  3         10  
  3         7  
81             {
82 3         8 $_protocol = "I2C";
83 3         26 return Test::Future::Deferred->done_later( $self );
84             }
85              
86 2     2 0 5 method make_protocol_SPI ()
  2         6  
  2         5  
87             {
88 2         6 $_protocol = "SPI";
89 2         22 return Test::Future::Deferred->done_later( $self );
90             }
91              
92 1     1 0 2 method make_protocol_UART ()
  1         2  
  1         2  
93             {
94 1         2 $_protocol = "UART";
95 1         7 return Test::Future::Deferred->done_later( $self );
96             }
97              
98 0     0 0 0 method configure ( % )
  0         0  
  0         0  
99             {
100 0         0 Test::Future::Deferred->done_later;
101             }
102              
103             field $_read_buffer;
104              
105             method use_read_buffer
106             {
107             require Future::Buffer;
108             $_read_buffer = Future::Buffer->new;
109             }
110              
111 1     1 0 65 method write_read_buffer ( $data )
  1         2  
  1         2  
  1         17  
112             {
113 1   33     5 ( $_read_buffer or croak "Read buffer is not enabled" )
114             ->write( $data );
115             }
116              
117             =head1 EXPECTATIONS
118              
119             Each of the actual methods to be used by the L under test has an
120             associated expectation method, whose name is prefixed C. Each returns
121             an expectation object, which has additional methods to control the behaviour of
122             that invocation.
123              
124             $exp = $adapter->expect_write_gpios( \%gpios );
125             $exp = $adapter->expect_read_gpios( \@gpios );
126             $exp = $adapter->expect_tris_gpios( \@gpios );
127             $exp = $adapter->expect_write( $bytes );
128             $exp = $adapter->expect_read( $len );
129             $exp = $adapter->expect_write_then_read( $bytes, $len );
130             $exp = $adapter->expect_readwrite( $bytes_out );
131             $exp = $adapter->expect_assert_ss;
132             $exp = $adapter->expect_release_ss;
133             $exp = $adapter->expect_readwrite_no_ss( $bytes_out );
134             $exp = $adapter->expect_write_no_ss( $bytes );
135              
136             The returned expectation object allows the test script to specify what such an
137             invocation should yield from its future.
138              
139             $exp->will_done( $bytes_in );
140             $exp->will_fail( $failure );
141              
142             Expectations for an atomic I²C transaction are performed inline, using the
143             following additional methods:
144              
145             $adapter->expect_txn_start();
146             $adapter->expect_txn_stop();
147              
148             As a lot of existing unit tests may have already been written to the API shape
149             provided by C version 0.03, the expectation
150             object also recognises the C method as an alias to C.
151              
152             $exp->returns( $bytes_in );
153              
154             This wrapper should be considered as a I back-compatibility measure
155             however. It now prints a warning and perhaps will be removed entirely in a
156             later version. You should avoid using it in new code; just call C
157             directly.
158              
159             =head2 Read Buffering
160              
161             I
162              
163             Testing with exact C calls can be fragile; especially with UART-based
164             protocols, as it relies on exact ordering, buffer sizes, and so on. A more
165             flexible approach that leads to less brittle tests is to use a buffer.
166              
167             This first has to be enabled:
168              
169             $adapter->use_read_buffer;
170              
171             At this point, no C method call will consume an expectation. Instead, it
172             will attempt to consume data from the read buffer. This can be provided by:
173              
174             $adapter->write_read_buffer( $data );
175              
176             =cut
177              
178             BEGIN {
179             my %METHODS = (
180             sleep => [ undef,
181             [qw( GPIO SPI I2C UART )] ],
182 2 100       7 write_gpios => [ sub { my ( $v ) = @_; join ",", map { $v->{$_} ? $_ : "!$_" } sort keys %$v },
  2         9  
  4         26  
183             [qw( GPIO SPI I2C UART )] ],
184 2         6 read_gpios => [ sub { my ( $v ) = @_; join ",", @$v },
  2         10  
185             [qw( GPIO SPI I2C UART )] ],
186 0         0 tris_gpios => [ sub { my ( $v ) = @_; join ",", @$v },
  0         0  
187 6     6   16569 [qw( GPIO SPI I2C UART )] ],
188             write => [ undef,
189             [qw( SPI I2C UART )] ],
190             read => [ undef,
191             [qw( SPI I2C UART )] ],
192             write_then_read => [ undef,
193             [qw( SPI I2C )] ],
194             readwrite => [ undef,
195             [qw( SPI )] ],
196             assert_ss => [ undef,
197             [qw( SPI )] ],
198             release_ss => [ undef,
199             [qw( SPI )] ],
200             write_no_ss => [ undef,
201             [qw( SPI )] ],
202             readwrite_no_ss => [ undef,
203             [qw( SPI )] ],
204             );
205              
206 6     6   119 use Object::Pad 0.800 ':experimental(mop)';
  6         116  
  6         417  
207 6         52 my $meta = Object::Pad::MOP::Class->for_caller;
208              
209 6         511 foreach my $method ( keys %METHODS ) {
210 72         208 my ( $canonicalise, $allowed_protos ) = $METHODS{$method}->@*;
211              
212             $meta->add_method(
213             "expect_$method" => method {
214 37 100       26718 @_ = $canonicalise->( @_ ) if $canonicalise;
215              
216 37         228 return $_controller->expect( $method => @_ )
217             ->will_done();
218 72         785 }
219             );
220              
221             $meta->add_method(
222             "$method" => method {
223 38 100       5495 @_ = $canonicalise->( @_ ) if $canonicalise;
224              
225 38         119 my @args = @_;
226              
227 38 50   61   317 any { $_ eq $_protocol } @$allowed_protos or
  61         202  
228             croak "Method ->$method not allowed in $_protocol protocol";
229              
230 38 100 66     227 if( $method eq "read" and $_read_buffer ) {
231 1         3 my ( $len ) = @args;
232 1         2 return $_read_buffer->read_exactly( $len );
233             }
234              
235 37         342 return $_obj->$method( @args );
236 72         884 }
237             );
238             }
239              
240             class Test::Device::Chip::Adapter::_TxnHelper {
241 6         9 field $_adapter :param;
242              
243 6     0   13 async method write { await $_adapter->write( @_ ) }
  0         0  
  0         0  
244 6     0   8 async method read { return await $_adapter->read( @_ ) }
  0         0  
  0         0  
245 6     0   13 async method write_then_read { return await $_adapter->write_then_read( @_ ) }
  0         0  
  0         0  
246             }
247              
248 6     0 0 23 async method txn ( $code )
  0         0  
  0         0  
  0         0  
  0         0  
249 0         0 {
250 0 0       0 $_protocol eq "I2C" or
251             croak "Method ->txn not allowed in $_protocol protocol";
252              
253 0   0     0 $_txn_helper //= Test::Device::Chip::Adapter::_TxnHelper->new( adapter => $self );
254              
255 0         0 $_obj->txn_start;
256              
257 0         0 my $result = await $code->( $_txn_helper );
258              
259 0         0 $_obj->txn_stop;
260              
261 0         0 return $result;
262 6         11 }
263              
264 6     0 0 12 async method expect_txn_start () { $_controller->expect( txn_start => ) }
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
265 0     0 0 0 async method expect_txn_stop () { $_controller->expect( txn_stop => ) }
  0         0  
  0         0  
  0         0  
  0         0  
266             }
267              
268             =head1 METHODS
269              
270             This class has the methods available on L, which would
271             normally be used by the chip instance under test. The following additional
272             methods would be used by the unit test script controlling it.
273              
274             =cut
275              
276             =head2 check_and_clear
277              
278             $adapter->check_and_clear( $name );
279              
280             Checks that by now, every expected method has indeed been called, and emits a
281             new test output line via L. Regardless, the expectations are
282             also cleared out ready for the start of the next test.
283              
284             =cut
285              
286 30     30 1 65939 method check_and_clear ( $name )
  30         142  
  30         62  
  30         53  
287             {
288 30         203 $_controller->check_and_clear( $name );
289 30         130575 return;
290             }
291              
292             package # hide
293             Test::Device::Chip::Adapter::_TestController
294             {
295 6     6   57 use base "Test::ExpectAndCheck::Future";
  6         14  
  6         868  
296 6     6   42 use constant EXPECTATION_CLASS => "Test::Device::Chip::Adapter::_Expectation";
  6         12  
  6         825  
297             }
298              
299             package # hide
300             Test::Device::Chip::Adapter::_Expectation
301             {
302 6     6   45 use base "Test::ExpectAndCheck::Future::_Expectation";
  6         11  
  6         4018  
303              
304 0           sub returns ( $self, @result )
305 0     0     {
  0            
  0            
306 0           warnings::warnif deprecated =>
307             "Calling ->returns on a Test::Device::Chip::Adapter expectation is now deprecated; use ->will_done instead";
308 0           $self->will_done( @result );
309             }
310             }
311              
312             =head1 TODO
313              
314             =over 4
315              
316             =item *
317              
318             Handle C method
319              
320             =item *
321              
322             Handle C
323              
324             =back
325              
326             =head1 AUTHOR
327              
328             Paul Evans
329              
330             =cut
331              
332             0x55AA;