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   1662223 use 5.008001;
  10         71  
4              
5 10     10   43 use strict;
  10         16  
  10         192  
6 10     10   54 use warnings;
  10         22  
  10         306  
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   5319 no if $] ge '5.020', feature => qw{ signatures };
  10         115  
  10         44  
11              
12 10     10   1254 use Carp;
  10         19  
  10         468  
13 10     10   48 use Exporter 5.567; # Comes with Perl 5.8.1.
  10         129  
  10         311  
14             # use File::Find ();
15             # use File::Spec ();
16             # use Getopt::Long 2.34; # Comes with Perl 5.8.1.
17 10     10   46 use Test2::API 1.302096 ();
  10         127  
  10         204  
18 10     10   46 use Test2::API::Context 1.302096 (); # for pass_and_release().
  10         137  
  10         230  
19 10     10   47 use Test2::Util 1.302096 ();
  10         198  
  10         209  
20              
21 10     10   43 use base qw{ Exporter };
  10         18  
  10         2251  
22              
23             our $VERSION = '0.008';
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   66 use constant ARRAY_REF => ref [];
  10         16  
  10         790  
68 10     10   56 use constant HASH_REF => ref {};
  10         26  
  10         516  
69              
70 10     10   53 use constant CALLER_HINT_HASH => 10;
  10         17  
  10         453  
71              
72 10     10   49 use constant DEFAULT_LOAD_ERROR => '%s';
  10         18  
  10         447  
73              
74 10         443 use constant ERR_IMPORT_BAD =>
75 10     10   49 'Import list must be an array reference, or undef';
  10         16  
76 10     10   46 use constant ERR_MODULE_UNDEF => 'Module name must be defined';
  10         27  
  10         439  
77 10     10   51 use constant ERR_OPTION_BAD => 'Bad option';
  10         16  
  10         488  
78 10         415 use constant ERR_SKIP_NUM_BAD =>
79 10     10   54 'Number of skipped tests must be an unsigned integer';
  10         17  
80 10     10   50 use constant ERR_VERSION_BAD => q/Version '%s' is invalid/;
  10         16  
  10         523  
81              
82 10     10   55 use constant HINTS_AVAILABLE => $] ge '5.010';
  10         22  
  10         1113  
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         557 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   61 ) )/;
  10         15  
96              
97 10     10   120 use constant TEST_MORE_ERROR_CONTEXT => q/Tried to %s '%s'./;
  10         16  
  10         591  
98 10     10   62 use constant TEST_MORE_LOAD_ERROR => 'Error: %s';
  10         12  
  10         502  
99 10         6810 use constant TEST_MORE_OPT => {
100             load_error => TEST_MORE_LOAD_ERROR,
101             require => 1,
102 10     10   49 };
  10         17  
103              
104             {
105             my %module_tried;
106              
107             sub load_module_ok (@) { ## no critic (RequireArgUnpacking)
108 16     16 1 39382 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         22 $module_tried{ $arg[1] } = undef;
113              
114 12         32 my $ctx = Test2::API::context();
115              
116 12         860 my $rslt = _load_module_ok( @arg );
117              
118 12         1481 $module_tried{ $arg[1] } = $rslt;
119              
120 12         33 $ctx->release();
121              
122 12         258 return $rslt;
123             }
124              
125             sub all_modules_tried_ok (@) {
126 3     3 1 5627 my @where = @_;
127             @where
128 3 50       10 or @where = ( 'blib/lib', 'blib/arch' );
129              
130 3         16 require File::Find;
131 3         8 require File::Spec;
132              
133 3         5 my @not_tried;
134 3         5 foreach my $d ( @where ) {
135             File::Find::find( sub {
136 15 100   15   798 m/ [.] pm \z /smx
137             or return;
138 3         262 my ( undef, $dir, $name ) = File::Spec->splitpath(
139             File::Spec->abs2rel( $File::Find::name, $d ) );
140 3         16 my @dir = File::Spec->splitdir( $dir );
141 3 50       8 $dir[-1]
142             or pop @dir;
143 3         15 ( my $module = join '::', @dir, $name ) =~ s/ [.] pm //smx;
144 3 100       89 exists $module_tried{$module}
145             or push @not_tried, $module;
146 6         325 }, $d );
147             }
148              
149 3 100       14 if ( @not_tried ) {
150              
151 2         7 my $ctx = Test2::API::context();
152              
153 2         165 $ctx->fail( "Module $_ not tried" ) for sort @not_tried;
154              
155 2         312 $ctx->release();
156              
157 2         54 return 0;
158             }
159             }
160              
161             sub clear_modules_tried () {
162 1     1 1 522 %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         33 local $@ = undef;
171              
172 21         46 my $eval = __build_load_eval( $opt, $module, $version, $import );
173              
174 21 100       50 defined $name
175             or $name = $eval;
176              
177 21         46 my $ctx = Test2::API::context();
178              
179 21 100       1098 _eval_in_pkg( $eval, $ctx->trace()->call() )
180             and return $ctx->pass_and_release( $name );
181              
182 10         53 chomp $@;
183              
184             $opt->{load_error}
185 10 100       50 and push @diag, sprintf $opt->{load_error}, $@;
186              
187 10         40 return $ctx->fail_and_release( $name, @diag );
188             }
189              
190             sub load_module_or_skip (@) { ## no critic (RequireArgUnpacking,RequireFinalReturn)
191 11     11 1 26334 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       29 defined $name
197             or $name = sprintf 'Unable to %s',
198             __build_load_eval( $opt, $module, $version, $import );
199 4 100 100     114 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     230 $num ||= 1;
205 3         14 $ctx->skip( 'skipped test', $name ) for 1 .. $num;
206              
207 3         919 $ctx->release();
208 10     10   67 no warnings qw{ exiting };
  10         15  
  10         15147  
209 3         67 last SKIP;
210             }
211              
212             sub load_module_or_skip_all (@) { ## no critic (RequireArgUnpacking)
213 10     10 1 21405 my ( $opt, $module, $version, $import, $name ) = _validate_args( 4, @_ );
214              
215 5 100       13 _load_module( $opt, $module, $version, $import )
216             and return;
217              
218 3 50       27 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         236 $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         17 local $@ = undef;
233              
234 11         22 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   91 my ( $opt ) = @_;
250 50   50     107 $opt ||= {};
251             {
252 50 100       71 unless ( $psr ) {
  50         114  
253 9         5622 require Getopt::Long;
254 9         78384 Getopt::Long->VERSION( 2.34 );
255 9         175 $psr = Getopt::Long::Parser->new();
256 9         186 $psr->configure( qw{ posix_default } );
257             }
258              
259 50         553 my $opt_err;
260 50     3   239 local $SIG{__WARN__} = sub { $opt_err = $_[0] };
  3         651  
261             $psr->getoptions( $opt, qw{
262             load_error=s
263             require|req!
264             },
265 50 100       151 ) or do {
266 3 50       149 if ( defined $opt_err ) {
267 3         5 chomp $opt_err;
268 3         272 croak $opt_err;
269             } else {
270 0         0 croak ERR_OPTION_BAD;
271             }
272             };
273             }
274 47 100       11596 if ( $opt->{load_error} ) {
275             $opt->{load_error} =~ m/ ( %+ ) [ #0+-]* [0-9]* s /smx
276             and length( $1 ) % 2
277 34 100 66     283 or $opt->{load_error} = '%s';
278             }
279 47         89 return $opt;
280             }
281             }
282              
283             sub import { ## no critic (RequireArgUnpacking,ProhibitBuiltinHomonyms)
284 18     18   8896 ( my $class, local @ARGV ) = @_; # See _parse_opts
285 18 100       58 if ( @ARGV ) {
286 13         21 my %opt;
287 13         32 _parse_opts( \%opt );
288 13         18 if ( HINTS_AVAILABLE ) {
289 13         36 $^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       4049 or return;
296             }
297 14         4069 return $class->export_to_level( 1, $class, @ARGV );
298             }
299              
300             sub require_ok ($) {
301 4     4 1 18884 my ( $module ) = @_;
302 4 100       80 defined $module
303             or croak ERR_MODULE_UNDEF;
304 3         7 my $ctx = Test2::API::context();
305 3         210 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         452 $ctx->release();
310 3         63 return $rslt;
311             }
312              
313             sub use_ok ($;@) {
314 7     7 1 16211 my ( $module, @arg ) = @_;
315 7 100       81 defined $module
316             or croak ERR_MODULE_UNDEF;
317 6 100 100     46 my $version = ( defined $arg[0] && $arg[0] =~ LAX_VERSION ) ?
318             shift @arg : undef;
319 6         14 my $ctx = Test2::API::context();
320 6         441 my $rslt = _load_module_ok( TEST_MORE_OPT,
321             $module, $version, \@arg, undef,
322             sprintf( TEST_MORE_ERROR_CONTEXT, use => $module ),
323             );
324 6         876 $ctx->release();
325 6         124 return $rslt;
326             }
327              
328             sub _make_pragma_key {
329 4     4   22 return join '', __PACKAGE__, '/', $_;
330             }
331              
332             sub _caller_class {
333 4     4   8 my ( $lvl ) = @_;
334 4   50     25 my ( $pkg ) = caller( $lvl || 1 );
335 4 100       323 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   605 my ( $level ) = @_;
348 42   100     104 $level ||= 0;
349 42         232 my $hint_hash = ( caller( $level ) )[ CALLER_HINT_HASH ];
350 42         182 my %rslt = %default_hint;
351 42         63 if ( HINTS_AVAILABLE ) {
352 42         53 foreach ( keys %{ $hint_hash } ) {
  42         111  
353 4         29 my ( $hint_pkg, $hint_key ) = split qr< / >smx;
354             __PACKAGE__ eq $hint_pkg
355 4 50       20 and $rslt{$hint_key} = $hint_hash->{$_};
356             }
357             }
358 42         151 return \%rslt;
359             }
360             }
361              
362             sub __build_load_eval {
363 73     73   22957 my @arg = @_;
364 73 100       216 HASH_REF eq ref $arg[0]
365             or unshift @arg, {};
366 73         141 my ( $opt, $module, $version, $import ) = @arg;
367 73         153 my @eval = "use $module";
368              
369 73 100       152 defined $version
370             and push @eval, $version;
371              
372 73 100 100     296 if ( $import && @{ $import } ) {
  29 100 100     102  
373 19         32 push @eval, "qw{ @{ $import } }";
  19         47  
374             } elsif ( defined $import xor not $opt->{require} ) {
375             # Do nothing.
376             } else {
377 12         23 push @eval, '()';
378             }
379              
380 73         409 return "@eval;";
381             }
382              
383             sub _validate_args {
384 37     37   104 ( my $max_arg, local @ARGV ) = @_;
385 37         66 my $opt = _parse_opts( __get_hint_hash( 2 ) );
386              
387 34 100 100     118 if ( $max_arg && @ARGV > $max_arg ) {
388 2         16 ( my $sub_name = ( caller 1 )[3] ) =~ s/ .* :: //smx;
389 2         173 croak sprintf '%s() takes at most %d arguments', $sub_name, $max_arg;
390             }
391              
392 32         76 my ( $module, $version, $import, $name, @diag ) = @ARGV;
393              
394 32 100       62 defined $module
395             or $module = _caller_class( 2 );
396              
397 29 100       56 if ( defined $version ) {
398 7 100       295 $version =~ LAX_VERSION
399             or croak sprintf ERR_VERSION_BAD, $version;
400             }
401              
402 26 100 100     364 not defined $import
403             or ARRAY_REF eq ref $import
404             or croak ERR_IMPORT_BAD;
405              
406 23         79 return ( $opt, $module, $version, $import, $name, @diag );
407             }
408              
409             sub _eval_in_pkg {
410 32     32   214 my ( $eval, $pkg, $file, $line ) = @_;
411              
412 32         107 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         1654 my $rslt = eval $e; ## no critic (ProhibitStringyEval)
424              
425 32         4299 return $rslt;
426             }
427              
428             sub _get_call_info {
429 11     11   17 my $lvl = 0;
430 11         106 while ( my @info = caller $lvl++ ) {
431 33 100       184 __PACKAGE__ eq $info[0]
432             and next;
433 11 50       80 $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__