File Coverage

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