File Coverage

blib/lib/Test/Device/Chip/Adapter.pm
Criterion Covered Total %
statement 82 118 69.4
branch 7 10 70.0
condition 0 3 0.0
subroutine 16 23 69.5
pod 1 9 11.1
total 106 163 65.0


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   441450 use v5.26;
  6         103  
7 6     6   2939 use Object::Pad 0.66; # field
  6         50136  
  6         28  
8              
9             package Test::Device::Chip::Adapter 0.23;
10             class Test::Device::Chip::Adapter
11 6     6   3415 :does(Device::Chip::Adapter);
  6         14  
  6         210  
12              
13 6     6   809 use Carp;
  6         10  
  6         373  
14              
15 6     6   32 use Future::AsyncAwait;
  6         11  
  6         21  
16              
17 6     6   2787 use Test::Future::Deferred;
  6         2588  
  6         242  
18 6     6   35 use List::Util 1.33 qw( first any );
  6         102  
  6         577  
19 6     6   38 use Test::Builder;
  6         49  
  6         141  
20              
21 6     6   2582 use Test::ExpectAndCheck::Future;
  6         67019  
  6         5517  
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             ->returns( "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::ExpectAndCheck::Future->create;
71             }
72              
73 1         2 method make_protocol_GPIO ()
  1         2  
74 1     1 0 2 {
75 1         2 $_protocol = "GPIO";
76 1         5 return Test::Future::Deferred->done_later( $self );
77             }
78              
79 3         4 method make_protocol_I2C ()
  3         5  
80 3     3 0 6 {
81 3         7 $_protocol = "I2C";
82 3         12 return Test::Future::Deferred->done_later( $self );
83             }
84              
85 2         5 method make_protocol_SPI ()
  2         3  
86 2     2 0 5 {
87 2         3 $_protocol = "SPI";
88 2         13 return Test::Future::Deferred->done_later( $self );
89             }
90              
91 1         1 method make_protocol_UART ()
  1         2  
92 1     1 0 2 {
93 1         2 $_protocol = "UART";
94 1         4 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 return or throw.
123              
124             $exp->returns( $bytes_in );
125             $exp->fails( $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             =cut
134              
135             BEGIN {
136             my %METHODS = (
137             sleep => [ undef,
138             [qw( GPIO SPI I2C UART )] ],
139 2 100       3 write_gpios => [ sub { my ( $v ) = @_; join ",", map { $v->{$_} ? $_ : "!$_" } sort keys %$v },
  2         9  
  4         17  
140             [qw( GPIO SPI I2C UART )] ],
141 2         7 read_gpios => [ sub { my ( $v ) = @_; join ",", @$v },
  2         6  
142             [qw( GPIO SPI I2C UART )] ],
143 0         0 tris_gpios => [ sub { my ( $v ) = @_; join ",", @$v },
  0         0  
144 6     6   8827 [qw( GPIO SPI I2C UART )] ],
145             write => [ undef,
146             [qw( SPI I2C UART )] ],
147             read => [ undef,
148             [qw( SPI I2C UART )] ],
149             write_then_read => [ undef,
150             [qw( SPI I2C )] ],
151             readwrite => [ undef,
152             [qw( SPI )] ],
153             assert_ss => [ undef,
154             [qw( SPI )] ],
155             release_ss => [ undef,
156             [qw( SPI )] ],
157             write_no_ss => [ undef,
158             [qw( SPI )] ],
159             readwrite_no_ss => [ undef,
160             [qw( SPI )] ],
161             );
162              
163 6     6   54 use Object::Pad ':experimental(mop)';
  6         10  
  6         34  
164 6         32 my $meta = Object::Pad::MOP::Class->for_caller;
165              
166 6         228 foreach my $method ( keys %METHODS ) {
167 72         148 my ( $canonicalise, $allowed_protos ) = $METHODS{$method}->@*;
168              
169             $meta->add_method(
170 37         16416 "expect_$method" => method {
171 37 100       103 @_ = $canonicalise->( @_ ) if $canonicalise;
172              
173 37         167 return $_controller->expect( $method => @_ );
174 72         577 }
175             );
176              
177             $meta->add_method(
178 37         394 "$method" => method {
179 37 100       80 @_ = $canonicalise->( @_ ) if $canonicalise;
180              
181 37         79 my @args = @_;
182              
183 37 50       176 any { $_ eq $_protocol } @$allowed_protos or
  58         120  
184             croak "Method ->$method not allowed in $_protocol protocol";
185              
186 37         241 return $_obj->$method( @args );
187 72         669 }
188             );
189             }
190              
191             class Test::Device::Chip::Adapter::_TxnHelper {
192 6         9 field $_adapter :param;
193              
194 6     0   9 async method write { await $_adapter->write( @_ ) }
  0         0  
  0         0  
  0         0  
195 6     0   9 async method read { return await $_adapter->read( @_ ) }
  0         0  
  0         0  
  0         0  
196 6     0   9 async method write_then_read { return await $_adapter->write_then_read( @_ ) }
  0         0  
  0         0  
  0         0  
197             }
198              
199 6         11 async method txn ( $code )
  0         0  
  0         0  
  0         0  
200 0         0 {
201 0 0       0 $_protocol eq "I2C" or
202             croak "Method ->txn not allowed in $_protocol protocol";
203              
204 0   0     0 $_txn_helper //= Test::Device::Chip::Adapter::_TxnHelper->new( adapter => $self );
205              
206 0         0 $_obj->txn_start;
207              
208 0         0 my $result = await $code->( $_txn_helper );
209              
210 0         0 $_obj->txn_stop;
211              
212 0         0 return $result;
213 6     0 0 8 }
  0         0  
214              
215 6     0 0 13 async method expect_txn_start () { $_controller->expect( txn_start => ) }
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
216 0     0 0 0 async method expect_txn_stop () { $_controller->expect( txn_stop => ) }
  0         0  
  0         0  
  0         0  
  0         0  
217             }
218              
219             =head1 METHODS
220              
221             This class has the methods available on L, which would
222             normally be used by the chip instance under test. The following additional
223             methods would be used by the unit test script controlling it.
224              
225             =cut
226              
227             =head2 check_and_clear
228              
229             $adapter->check_and_clear( $name );
230              
231             Checks that by now, every expected method has indeed been called, and emits a
232             new test output line via L. Regardless, the expectations are
233             also cleared out ready for the start of the next test.
234              
235             =cut
236              
237 29         47 method check_and_clear ( $name )
  29         52  
  29         38  
238 29     29 1 57093 {
239 29         109 $_controller->check_and_clear( $name );
240 29         60248 return;
241             }
242              
243             =head1 TODO
244              
245             =over 4
246              
247             =item *
248              
249             Handle C method
250              
251             =item *
252              
253             Handle C
254              
255             =back
256              
257             =head1 AUTHOR
258              
259             Paul Evans
260              
261             =cut
262              
263             0x55AA;