File Coverage

blib/lib/Test2/Tools/LoadModule.pm
Criterion Covered Total %
statement 212 216 98.1
branch 65 72 90.2
condition 27 30 90.0
subroutine 45 45 100.0
pod 7 7 100.0
total 356 370 96.2


line stmt bran cond sub pod time code
1             package Test2::Tools::LoadModule;
2              
3 10     10   1610004 use 5.008001;
  10         36  
4              
5 10     10   48 use strict;
  10         22  
  10         269  
6 10     10   31 use warnings;
  10         15  
  10         504  
7              
8             # OK, the following is probably paranoia. But if Perl 7 decides to
9             # change this particular default I'm ready. Unless they eliminate $].
10 10     10   44 no if $] ge '5.020', feature => qw{ signatures };
  10         35  
  10         1525  
11              
12 10     10   46 use Carp;
  10         14  
  10         678  
13 10     10   41 use Exporter 5.567; # Comes with Perl 5.8.1.
  10         142  
  10         322  
14             # use File::Find ();
15             # use File::Spec ();
16             # use Getopt::Long 2.34; # Comes with Perl 5.8.1.
17 10     10   35 use Test2::API 1.302096 ();
  10         174  
  10         225  
18 10     10   40 use Test2::API::Context 1.302096 (); # for pass_and_release().
  10         145  
  10         172  
19 10     10   31 use Test2::Util 1.302096 ();
  10         142  
  10         179  
20              
21 10     10   28 use base qw{ Exporter };
  10         17  
  10         2364  
22              
23             our $VERSION = '0.010';
24             $VERSION =~ s/ _ //smxg;
25              
26             {
27             my @test2 = qw{
28             all_modules_tried_ok
29             clear_modules_tried
30             load_module_ok
31             load_module_or_skip
32             load_module_or_skip_all
33             };
34              
35             my @more = qw{
36             require_ok
37             use_ok
38             };
39              
40             my @private = qw{
41             __build_load_eval
42             __get_hint_hash
43             DEFAULT_LOAD_ERROR
44             ERR_IMPORT_BAD
45             ERR_MODULE_UNDEF
46             ERR_OPTION_BAD
47             ERR_SKIP_NUM_BAD
48             ERR_VERSION_BAD
49             HINTS_AVAILABLE
50             TEST_MORE_ERROR_CONTEXT
51             TEST_MORE_LOAD_ERROR
52             };
53              
54             our @EXPORT_OK = ( @test2, @more, @private );
55              
56             our %EXPORT_TAGS = (
57             all => [ @test2, @more ],
58             default => \@test2,
59             more => \@more,
60             private => \@private,
61             test2 => \@test2,
62             );
63              
64             our @EXPORT = @{ $EXPORT_TAGS{default} }; ## no critic (ProhibitAutomaticExportation)
65             }
66              
67 10     10   52 use constant ARRAY_REF => ref [];
  10         16  
  10         548  
68 10     10   38 use constant HASH_REF => ref {};
  10         12  
  10         420  
69              
70 10     10   34 use constant CALLER_HINT_HASH => 10;
  10         14  
  10         409  
71              
72 10     10   57 use constant DEFAULT_LOAD_ERROR => '%s';
  10         25  
  10         367  
73              
74 10         321 use constant ERR_IMPORT_BAD =>
75 10     10   51 'Import list must be an array reference, or undef';
  10         39  
76 10     10   41 use constant ERR_MODULE_UNDEF => 'Module name must be defined';
  10         16  
  10         342  
77 10     10   31 use constant ERR_OPTION_BAD => 'Bad option';
  10         17  
  10         385  
78 10         317 use constant ERR_SKIP_NUM_BAD =>
79 10     10   42 'Number of skipped tests must be an unsigned integer';
  10         13  
80 10     10   31 use constant ERR_VERSION_BAD => q/Version '%s' is invalid/;
  10         19  
  10         357  
81              
82 10     10   33 use constant HINTS_AVAILABLE => $] ge '5.010';
  10         29  
  10         1265  
83              
84             # The following cribbed shamelessly from version::regex 0.9924,
85             # after being munged to suit by tools/version_regex 0.000_010.
86             # This technical debt is incurred to avoid having to require a version
87             # of the version module large enough to export the is_lax() subroutine.
88 10         471 use constant LAX_VERSION => qr/(?x: (?x:
89             v (?-x:[0-9]+) (?-x: (?-x:\.[0-9]+)+ (?-x:_[0-9]+)? )?
90             |
91             (?-x:[0-9]+)? (?-x:\.[0-9]+){2,} (?-x:_[0-9]+)?
92             ) | (?x: (?-x:[0-9]+) (?-x: (?-x:\.[0-9]+) | \. )? (?-x:_[0-9]+)?
93             |
94             (?-x:\.[0-9]+) (?-x:_[0-9]+)?
95 10     10   78 ) )/;
  10         19  
96              
97 10     10   49 use constant TEST_MORE_ERROR_CONTEXT => q/Tried to %s '%s'./;
  10         13  
  10         330  
98 10     10   31 use constant TEST_MORE_LOAD_ERROR => 'Error: %s';
  10         13  
  10         390  
99 10         7072 use constant TEST_MORE_OPT => {
100             load_error => TEST_MORE_LOAD_ERROR,
101             require => 1,
102 10     10   34 };
  10         32  
103              
104             {
105             my %module_tried;
106              
107             sub load_module_ok (@) { ## no critic (RequireArgUnpacking)
108 16     16 1 170832 my @arg = _validate_args( 0, @_ );
109              
110             # We do this now in case _load_module_ok() throws an uncaught
111             # exception, just so we have SOME record we tried.
112 12         25 $module_tried{ $arg[1] } = undef;
113              
114 12         27 my $ctx = Test2::API::context();
115              
116 12         964 my $rslt = _load_module_ok( @arg );
117              
118 12         1627 $module_tried{ $arg[1] } = $rslt;
119              
120 12         32 $ctx->release();
121              
122 12         289 return $rslt;
123             }
124              
125             sub all_modules_tried_ok (@) {
126 3     3 1 5652 my @where = @_;
127             @where
128 3 50       11 or @where = ( 'blib/lib', 'blib/arch' );
129              
130 3         32 require File::Find;
131 3         8 require File::Spec;
132              
133 3         4 my @not_tried;
134 3         5 foreach my $d ( @where ) {
135             File::Find::find( sub {
136 15 100   15   902 m/ [.] pm \z /smx
137             or return;
138 3         250 my ( undef, $dir, $name ) = File::Spec->splitpath(
139             File::Spec->abs2rel( $File::Find::name, $d ) );
140 3         13 my @dir = File::Spec->splitdir( $dir );
141 3 50       8 $dir[-1]
142             or pop @dir;
143 3         13 ( my $module = join '::', @dir, $name ) =~ s/ [.] pm //smx;
144 3 100       74 exists $module_tried{$module}
145             or push @not_tried, $module;
146 6         334 }, $d );
147             }
148              
149 3 100       13 if ( @not_tried ) {
150              
151 2         7 my $ctx = Test2::API::context();
152              
153 2         262 $ctx->fail( "Module $_ not tried" ) for sort @not_tried;
154              
155 2         326 $ctx->release();
156              
157 2         63 return 0;
158             }
159             }
160              
161             sub clear_modules_tried () {
162 1     1 1 547 %module_tried = ();
163 1         3 return;
164             }
165             }
166              
167             sub _load_module_ok {
168 21     21   62 my ( $opt, $module, $version, $import, $name, @diag ) = @_;
169              
170 21         27 local $@ = undef;
171              
172 21         47 my $eval = __build_load_eval( $opt, $module, $version, $import );
173              
174 21 100       44 defined $name
175             or $name = $eval;
176              
177 21         56 my $ctx = Test2::API::context();
178              
179 21 100       1025 _eval_in_pkg( $eval, $ctx->trace()->call() )
180             and return $ctx->pass_and_release( $name );
181              
182 10         23 chomp $@;
183              
184             $opt->{load_error}
185 10 100       46 and push @diag, sprintf $opt->{load_error}, $@;
186              
187 10         38 return $ctx->fail_and_release( $name, @diag );
188             }
189              
190             sub load_module_or_skip (@) { ## no critic (RequireArgUnpacking,RequireFinalReturn)
191 11     11 1 20797 my ( $opt, $module, $version, $import, $name, $num ) = _validate_args( 5, @_ );
192              
193 6 100       16 _load_module( $opt, $module, $version, $import )
194             and return;
195              
196 4 50       19 defined $name
197             or $name = sprintf 'Unable to %s',
198             __build_load_eval( $opt, $module, $version, $import );
199 4 100 100     127 defined $num
200             and $num =~ m/ [^0-9] /smx
201             and croak ERR_SKIP_NUM_BAD;
202              
203 3         9 my $ctx = Test2::API::context();
204 3   100     263 $num ||= 1;
205 3         15 $ctx->skip( 'skipped test', $name ) for 1 .. $num;
206              
207 3         846 $ctx->release();
208 10     10   83 no warnings qw{ exiting };
  10         18  
  10         15903  
209 3         79 last SKIP;
210             }
211              
212             sub load_module_or_skip_all (@) { ## no critic (RequireArgUnpacking)
213 10     10 1 16117 my ( $opt, $module, $version, $import, $name ) = _validate_args( 4, @_ );
214              
215 5 100       12 _load_module( $opt, $module, $version, $import )
216             and return;
217              
218 3 50       11 defined $name
219             or $name = sprintf 'Unable to %s',
220             __build_load_eval( $opt, $module, $version, $import );
221              
222 3         10 my $ctx = Test2::API::context();
223 3         245 $ctx->plan( 0, SKIP => $name );
224 0         0 $ctx->release();
225              
226 0         0 return;
227             }
228              
229             sub _load_module {
230 11     11   23 my ( $opt, $module, $version, $import ) = @_;
231              
232 11         20 local $@ = undef;
233              
234 11         55 my $eval = __build_load_eval( $opt, $module, $version, $import );
235              
236 11         26 return _eval_in_pkg( $eval, _get_call_info() )
237             }
238              
239             {
240             my $psr;
241              
242             # Because we want to work with Perl 5.8.1 we are limited to
243             # Getopt::Long 2.34, and therefore getoptions(). So we expect the
244             # arguments to be in a suitably-localized @ARGV. The optional
245             # argument is a reference to a hash into which we place the option
246             # values. If omitted, we create a reference to a new hash. Either
247             # way the hash reference gets returned.
248             sub _parse_opts {
249 50     50   76 my ( $opt ) = @_;
250 50   50     106 $opt ||= {};
251             {
252 50 100       64 unless ( $psr ) {
  50         106  
253 9         5760 require Getopt::Long;
254 9         83597 Getopt::Long->VERSION( 2.34 );
255 9         175 $psr = Getopt::Long::Parser->new();
256 9         7866 $psr->configure( qw{ posix_default } );
257             }
258              
259 50         661 my $opt_err;
260 50     3   251 local $SIG{__WARN__} = sub { $opt_err = $_[0] };
  3         628  
261             $psr->getoptions( $opt, qw{
262             load_error=s
263             require|req!
264             },
265 50 100       166 ) or do {
266 3 50       138 if ( defined $opt_err ) {
267 3         4 chomp $opt_err;
268 3         334 croak $opt_err;
269             } else {
270 0         0 croak ERR_OPTION_BAD;
271             }
272             };
273             }
274 47 100       14123 if ( $opt->{load_error} ) {
275             $opt->{load_error} =~ m/ ( %+ ) [ #0+-]* [0-9]* s /smx
276             and length( $1 ) % 2
277 34 100 66     273 or $opt->{load_error} = '%s';
278             }
279 47         77 return $opt;
280             }
281             }
282              
283             sub import { ## no critic (RequireArgUnpacking,ProhibitBuiltinHomonyms)
284 18     18   9096 ( my $class, local @ARGV ) = @_; # See _parse_opts
285 18 100       64 if ( @ARGV ) {
286 13         16 my %opt;
287 13         37 _parse_opts( \%opt );
288 13         19 if ( HINTS_AVAILABLE ) {
289 13         37 $^H{ _make_pragma_key() } = $opt{$_} for keys %opt;
290             } else {
291             keys %opt
292             and carp "Import options ignored under Perl $]";
293             }
294             @ARGV
295 13 100       3791 or return;
296             }
297 14         4191 return $class->export_to_level( 1, $class, @ARGV );
298             }
299              
300             sub require_ok ($) {
301 4     4 1 12629 my ( $module ) = @_;
302 4 100       82 defined $module
303             or croak ERR_MODULE_UNDEF;
304 3         5 my $ctx = Test2::API::context();
305 3         187 my $rslt = _load_module_ok( TEST_MORE_OPT,
306             $module, undef, undef, "require $module;",
307             sprintf( TEST_MORE_ERROR_CONTEXT, require => $module ),
308             );
309 3         458 $ctx->release();
310 3         81 return $rslt;
311             }
312              
313             sub use_ok ($;@) {
314 7     7 1 14654 my ( $module, @arg ) = @_;
315 7 100       85 defined $module
316             or croak ERR_MODULE_UNDEF;
317 6 100 100     45 my $version = ( defined $arg[0] && $arg[0] =~ LAX_VERSION ) ?
318             shift @arg : undef;
319 6         10 my $ctx = Test2::API::context();
320 6         359 my $rslt = _load_module_ok( TEST_MORE_OPT,
321             $module, $version, \@arg, undef,
322             sprintf( TEST_MORE_ERROR_CONTEXT, use => $module ),
323             );
324 6         837 $ctx->release();
325 6         144 return $rslt;
326             }
327              
328             sub _make_pragma_key {
329 4     4   21 return join '', __PACKAGE__, '/', $_;
330             }
331              
332             sub _caller_class {
333 4     4   9 my ( $lvl ) = @_;
334 4   50     18 my ( $pkg ) = caller( $lvl || 1 );
335 4 100       391 my $code = $pkg->can( 'CLASS' )
336             or croak ERR_MODULE_UNDEF;
337 1         3 return $code->();
338             }
339              
340             {
341              
342             my %default_hint = (
343             load_error => DEFAULT_LOAD_ERROR,
344             );
345              
346             sub __get_hint_hash {
347 42     42   137235 my ( $level ) = @_;
348 42   100     102 $level ||= 0;
349 42         185 my $hint_hash = ( caller( $level ) )[ CALLER_HINT_HASH ];
350 42         122 my %rslt = %default_hint;
351 42         53 if ( HINTS_AVAILABLE ) {
352 42         48 foreach ( keys %{ $hint_hash } ) {
  42         96  
353 4         30 my ( $hint_pkg, $hint_key ) = split qr< / >smx;
354             __PACKAGE__ eq $hint_pkg
355 4 50       21 and $rslt{$hint_key} = $hint_hash->{$_};
356             }
357             }
358 42         138 return \%rslt;
359             }
360             }
361              
362             sub __build_load_eval {
363 73     73   155989 my @arg = @_;
364 73 100       195 HASH_REF eq ref $arg[0]
365             or unshift @arg, {};
366 73         168 my ( $opt, $module, $version, $import ) = @arg;
367 73         135 my @eval = "use $module";
368              
369 73 100       147 defined $version
370             and push @eval, $version;
371              
372 73 100 100     331 if ( $import && @{ $import } ) {
  29 100 100     99  
373 19         29 push @eval, "qw{ @{ $import } }";
  19         42  
374             } elsif ( defined $import xor not $opt->{require} ) {
375             # Do nothing.
376             } else {
377 12         21 push @eval, '()';
378             }
379              
380 73         373 return "@eval;";
381             }
382              
383             sub _validate_args {
384 37     37   106 ( my $max_arg, local @ARGV ) = @_;
385 37         72 my $opt = _parse_opts( __get_hint_hash( 2 ) );
386              
387 34 100 100     100 if ( $max_arg && @ARGV > $max_arg ) {
388 2         13 ( my $sub_name = ( caller 1 )[3] ) =~ s/ .* :: //smx;
389 2         241 croak sprintf '%s() takes at most %d arguments', $sub_name, $max_arg;
390             }
391              
392 32         66 my ( $module, $version, $import, $name, @diag ) = @ARGV;
393              
394 32 100       68 defined $module
395             or $module = _caller_class( 2 );
396              
397 29 100       86 if ( defined $version ) {
398 7 100       328 $version =~ LAX_VERSION
399             or croak sprintf ERR_VERSION_BAD, $version;
400             }
401              
402 26 100 100     383 not defined $import
403             or ARRAY_REF eq ref $import
404             or croak ERR_IMPORT_BAD;
405              
406 23         82 return ( $opt, $module, $version, $import, $name, @diag );
407             }
408              
409             sub _eval_in_pkg {
410 32     32   207 my ( $eval, $pkg, $file, $line ) = @_;
411              
412 32         73 my $e = <<"EOD";
413             package $pkg;
414             #line $line "$file"
415             $eval;
416             1;
417             EOD
418              
419             # We need the stringy eval() so we can mess with Perl's concept of
420             # what the current file and line number are for the purpose of
421             # formatting the exception, AND as a convenience to get symbols
422             # imported.
423 32         2127 my $rslt = eval $e; ## no critic (ProhibitStringyEval)
424              
425 32         4815 return $rslt;
426             }
427              
428             sub _get_call_info {
429 11     11   16 my $lvl = 0;
430 11         98 while ( my @info = caller $lvl++ ) {
431 33 100       140 __PACKAGE__ eq $info[0]
432             and next;
433 11 50       53 $info[1] =~ m/ \A [(] eval \b /smx # )
434             or return @info;
435             }
436 0           confess 'Bug - Unable to determine caller';
437             }
438              
439             1;
440              
441             __END__