File Coverage

blib/lib/Test2/Tools/FFI.pm
Criterion Covered Total %
statement 113 124 91.1
branch 11 20 55.0
condition 6 12 50.0
subroutine 26 26 100.0
pod 1 2 50.0
total 157 184 85.3


line stmt bran cond sub pod time code
1             package Test2::Tools::FFI;
2              
3 4     4   2501 use strict;
  4         9  
  4         132  
4 4     4   21 use warnings;
  4         9  
  4         98  
5 4     4   78 use 5.008001;
  4         14  
6 4     4   24 use base qw( Exporter );
  4         23  
  4         524  
7 4     4   30 use FFI::Platypus 1.00;
  4         70  
  4         140  
8 4     4   25 use FFI::CheckLib 0.11 ();
  4         61  
  4         108  
9 4     4   28 use File::Basename ();
  4         9  
  4         70  
10 4     4   30 use Cwd ();
  4         8  
  4         93  
11 4     4   20 use File::Glob ();
  4         8  
  4         86  
12 4     4   18 use Test2::API qw( context );
  4         8  
  4         247  
13 4     4   42 use Test2::EventFacet::Trace;
  4         11  
  4         4889  
14              
15             # ABSTRACT: Tools for testing FFI
16             our $VERSION = '0.06'; # VERSION
17              
18             our @EXPORT = qw( ffi ffi_options );
19              
20              
21             {
22             my $singleton;
23              
24             sub ffi
25             {
26 11 100   11 0 87634 unless($singleton)
27             {
28 2         23 $singleton = bless {}, 'Test2::Tools::FFI::Single';
29             }
30              
31 11         62 $singleton;
32             }
33              
34              
35             sub ffi_options
36             {
37 1     1 1 121 my(%options) = @_;
38 1 50       6 Carp::croak("Please call ffi_options before calling ffi")
39             if defined $singleton;
40              
41 1         6 my $ffi = ffi();
42              
43 1         2 my @new_args;
44              
45 1 50       6 if(my $api = delete $options{api})
46             {
47 1         3 push @new_args, api => $api;
48             }
49              
50 1         7 $ffi->{new_args} = \@new_args;
51              
52 1 50       5 Carp::croak("Unknown option or options: @{[ sort keys %options ]}")
  0         0  
53             if %options;
54             }
55             }
56              
57             sub _pass
58             {
59 1     1   138 my($name, @location) = @_;
60 1         6 my $ctx = context();
61 1         104 $ctx->send_event(
62             'Pass',
63             name => $name,
64             # this seems to swallow some info, be good
65             # to know if we need it.
66             trace => Test2::EventFacet::Trace->new(
67             frame => [@location],
68             )
69             );
70 1         210 $ctx->release;
71             }
72              
73             sub _fail
74             {
75 1     1   44 my($name, @location) = @_;
76 1         4 my $ctx = context();
77 1         106 $ctx->send_event(
78             'Fail',
79             name => $name,
80             trace => Test2::EventFacet::Trace->new(
81             frame => [@location],
82             )
83             );
84 1         297 $ctx->release;
85             }
86              
87             sub _note
88             {
89 2     2   299 my($message, @location) = @_;
90 2         14 my $ctx = context();
91 2         219 $ctx->send_event(
92             'Note',
93             message => $message,
94             trace => Test2::EventFacet::Trace->new(
95             frame => [@location],
96             )
97             );
98 2         452 $ctx->release;
99             }
100              
101             sub _diag
102             {
103 2     2   88 my($message, @location) = @_;
104 2         6 my $ctx = context();
105 2         192 $ctx->send_event(
106             'Diag',
107             message => $message,
108             trace => Test2::EventFacet::Trace->new(
109             frame => [@location],
110             )
111             );
112 2         505 $ctx->release;
113             }
114              
115             {
116             local $ENV{FFI_PLATYPUS_DLERROR} = 1;
117             our $ffi = FFI::Platypus->new( api => 1 );
118             our @closures = map { $ffi->closure($_) } \&_note, \&_diag, \&_pass, \&_fail;
119             $ffi->bundle;
120             $ffi->type('(string,string,string,int,string)->void' => 'message_cb_t');
121             $ffi
122             ->function(t2t_simple_init => ['message_cb_t','message_cb_t','message_cb_t','message_cb_t'] => 'void')
123             ->call(@closures);
124             }
125              
126             package Test2::Tools::FFI::Single;
127              
128              
129             sub runtime
130             {
131 5     5   24 my($self) = @_;
132              
133             $self->{runtime} ||= (sub {
134 2     2   5 my $ffi = Test2::Tools::FFI::Platypus->new( @{ $self->{new_args} } );
  2         32  
135              
136 2         5620 my @dll = File::Glob::bsd_glob("blib/lib/auto/share/dist/*/lib/*");
137 2 50       16 if(@dll)
138             {
139 2         22 $ffi->lib(@dll);
140 2         60 return $ffi;
141             }
142              
143 0         0 @dll = File::Glob::bsd_glob("share/lib/*");
144 0 0       0 if(@dll)
145             {
146 0         0 $ffi->lib(@dll);
147 0         0 return $ffi;
148             }
149 0         0 $ffi;
150 5   66     49 })->();
151             }
152              
153              
154             sub _build_test
155             {
156 4 50   4   140 if(-d "t/ffi")
157             {
158 4         1361 require FFI::Build::MM;
159 4         336530 require Capture::Tiny;
160              
161             my($output, $error) = Capture::Tiny::capture_merged(sub {
162 4     4   6665 local $@ = '';
163 4         11 eval {
164 4         65 my $fbmm = FFI::Build::MM->new( save => 0 );
165 4         172 $fbmm->mm_args( DISTNAME => "My-Test" ); # the DISTNAME isn't used for building the test anyway.
166 4         16862 $fbmm->test->build;
167             };
168 4         120230 $@;
169 4         269 });
170 4 50       4338 if($error)
171             {
172 0         0 my $ctx = Test2::API::context();
173 0         0 $ctx->diag($error);
174 0         0 $ctx->diag($output);
175 0         0 $ctx->release;
176 0         0 die $error;
177             }
178             else
179             {
180 4         139 my $ctx = Test2::API::context();
181 4         1807 $ctx->note($output);
182 4         2940 $ctx->release;
183             }
184             }
185             }
186              
187             sub test
188             {
189 7     7   22 my($self) = @_;
190              
191 7   66     53 $self->{test} ||= do {
192 2         18 _build_test();
193 2         175 my $ffi = Test2::Tools::FFI::Platypus->new( @{ $self->{new_args} } );
  2         138  
194 2         3769 my @lib = FFI::CheckLib::find_lib(
195             lib => '*',
196             libpath => 't/ffi/_build',
197             systempath => [],
198             );
199 2 50       1033 Carp::croak("unable to find test lib in t/ffi/_build")
200             unless @lib;
201 2         57 $ffi->lib(@lib);
202 2         109 $ffi;
203             };
204             }
205              
206              
207             sub combined
208             {
209 2     2   74 my($self) = @_;
210              
211 2   33     38 $self->{combined} ||= do {
212 2         12 _build_test();
213 2         180 my $rt = $self->runtime;
214 2         49 my $t = $self->test;
215 2         19 my $ffi = Test2::Tools::FFI::Platypus->new( @{ $self->{new_args} } );
  2         101  
216 2         3571 $ffi->lib($rt->lib, $t->lib);
217 2         165 $ffi;
218             };
219             }
220              
221             package Test2::Tools::FFI::Platypus;
222              
223 4     4   38 use base qw( FFI::Platypus );
  4         7  
  4         390  
224 4     4   44 use Test2::API ();
  4         8  
  4         663  
225              
226             sub symbol_ok
227             {
228 9     9   17611 my($self, $symbol_name, $test_name) = @_;
229              
230 9   33     111 $test_name ||= "Library has symbol: $symbol_name";
231 9         42 my $address = $self->find_symbol($symbol_name);
232              
233 9         544 my $ctx = Test2::API::context();
234 9 100       906 if($address)
235             {
236 8         58 $ctx->pass_and_release($test_name);
237             }
238             else
239             {
240 1         5 $ctx->fail_and_release($test_name, map { "looked in $_" } $self->lib);
  1         14  
241             }
242             }
243              
244             1;
245              
246             __END__