File Coverage

inc/My/Module/Test/App.pm
Criterion Covered Total %
statement 159 261 60.9
branch 31 82 37.8
condition 11 25 44.0
subroutine 37 51 72.5
pod 14 19 73.6
total 252 438 57.5


line stmt bran cond sub pod time code
1             package My::Module::Test::App;
2              
3 19     19   2400731 use 5.008;
  19         73  
4              
5 19     19   108 use strict;
  19         53  
  19         585  
6 19     19   84 use warnings;
  19         34  
  19         1048  
7              
8 19     19   100 use Exporter ();
  19         139  
  19         1086  
9             our @ISA = qw{ Exporter };
10              
11 19     19   151 use Carp;
  19         50  
  19         1684  
12              
13 19     19   197 use Cwd qw{ abs_path };
  19         45  
  19         1260  
14 19     19   6884 use Getopt::Long 2.39 ();
  19         131944  
  19         1256  
15 19     19   5697 use POSIX qw{ strftime };
  19         96250  
  19         212  
16 19     19   20860 use Scalar::Util 1.26 qw{ blessed };
  19         609  
  19         1409  
17 19     19   1349 use Test2::V0;
  19         411883  
  19         162  
18 19     19   30417 use Test2::Util::Table qw{ table };
  19         38  
  19         231  
19              
20 19     19   1688 use constant CODE_REF => ref sub {};
  19         40  
  19         1959  
21 19     19   115 use constant REGEXP_REF => ref qr{};
  19         77  
  19         3411  
22              
23             our @EXPORT = qw{
24             any_greg_time_gm
25             any_greg_time_local
26             call_m
27             call_m_result
28             check_access
29             check_datetime_timezone_local
30             dependencies_table
31             dt_greg_time_gm
32             dt_greg_time_local
33             dump_date_manip
34             dump_date_manip_init
35             dump_zones
36             dump_zones_init
37             execute
38             invocant
39             klass
40             load_or_skip
41             mock_usgs
42             normalize_path
43             same_path
44             setup_app_mocker
45             FALSE
46             INSTANTIATE
47             TRUE
48             };
49              
50             {
51             local $@ = undef;
52              
53 19   50     14188 use constant HAVE_DATETIME => eval {
54             require DateTime;
55             require DateTime::TimeZone;
56             1;
57 19     19   193 } || 0;
  19         43  
58             }
59              
60             BEGIN {
61             # If I should need to write a test that uses a dirty environment (or
62             # at least wants something in $ENV{TZ}) the plan is to handle it via
63             # the import mechanism. See @EXPORT_FAIL, which is good back to at
64             # least 5.12. Except this may be causing problems with CPAN Testers
65             # machines set up correctly for zones other than their default.
66             # delete $ENV{TZ};
67              
68             # Note that we have to load Astro::App::Satpass2 this way because we
69             # need to clean up the environment before we do the load.
70 19     19   29403 require Astro::App::Satpass2;
71 19         2403 Astro::App::Satpass2->import();
72             }
73              
74             my $app = 'Astro::App::Satpass2';
75              
76             use constant FALSE => sub {
77 1         3 shift;
78 1         3 $_[0] = !$_[0];
79 1         3 goto &ok;
80 19     19   151 };
  19         38  
  19         2438  
81              
82             use constant INSTANTIATE => sub {
83 9         58 shift;
84 9         22 $app = $_[0];
85 9         58 goto &ok;
86 19     19   115 };
  19         35  
  19         1732  
87              
88             use constant TRUE => sub {
89 19         91 shift;
90 19         112 goto &ok;
91 19     19   111 };
  19         51  
  19         16981  
92              
93             if ( HAVE_DATETIME ) {
94             *any_greg_time_gm = \&dt_greg_time_gm;
95             *any_greg_time_local = \&dt_greg_time_local;
96             } else {
97             require Astro::Coord::ECI::Utils;
98             Astro::Coord::ECI::Utils->VERSION( 0.112 );
99             *any_greg_time_gm = \&Astro::Coord::ECI::Utils::greg_time_gm;
100             *any_greg_time_local = \&Astro::Coord::ECI::Utils::greg_time_local;
101             }
102              
103             sub invocant {
104 4     4 1 12577 return $app;
105             }
106              
107             sub check_access {
108 0     0 1 0 my ( $url ) = @_;
109              
110 0         0 local $@ = undef;
111              
112 0 0       0 eval {
113 0         0 require LWP::UserAgent;
114 0         0 1;
115             } or return 'Can not load LWP::UserAgent';
116              
117 0 0       0 my $ua = LWP::UserAgent->new()
118             or return 'Can not instantiate LWP::UserAgent';
119              
120 0 0       0 my $rslt = $ua->get( $url )
121             or return "Can not get $url";
122              
123 0 0       0 $rslt->is_success or return $rslt->status_line;
124              
125 0         0 return;
126             }
127              
128             sub check_datetime_timezone_local {
129 1     1 1 236232 local $@ = undef;
130 1 50       2 eval {
131 1         108 require DateTime;
132 0         0 require DateTime::TimeZone;
133 0         0 1;
134             } or return 1;
135 0         0 return eval {
136 0         0 DateTime::TimeZone->new( name => 'local' );
137 0         0 1;
138             };
139             }
140              
141             sub klass {
142 9     9 1 2193218 ( $app ) = @_;
143 9         29 return;
144             }
145              
146             sub _dtz_to_epoch {
147 0     0   0 my ( $sec, $min, $hr, $day, $mon, $yr, $zone ) = @_;
148 0         0 $mon += 1;
149 0         0 ( my $nano, $sec ) = POSIX::modf( $sec );
150 0         0 $nano *= 1_000_000_000;
151 0         0 return DateTime->new(
152             year => $yr,
153             month => $mon,
154             day => $day,
155             hour => $hr,
156             minute => $min,
157             second => $sec,
158             nanosecond => $nano,
159             time_zone => $zone,
160             )->epoch();
161             }
162              
163             {
164             my $tz_utc;
165              
166             sub dt_greg_time_gm {
167 0     0 1 0 my ( $sec, $min, $hr, $day, $mon, $yr ) = @_;
168 0   0     0 $tz_utc ||= DateTime::TimeZone->new( name => 'UTC' );
169 0         0 return _dtz_to_epoch( $sec, $min, $hr, $day, $mon, $yr, $tz_utc );
170             }
171             }
172              
173             {
174             my $tz_local;
175              
176             sub dt_greg_time_local {
177 0     0 1 0 my ( $sec, $min, $hr, $day, $mon, $yr ) = @_;
178 0   0     0 $tz_local ||= DateTime::TimeZone->new( name => 'local' );
179 0         0 return _dtz_to_epoch( $sec, $min, $hr, $day, $mon, $yr, $tz_local );
180             }
181             }
182              
183             {
184             my $dumped;
185              
186             sub dump_date_manip {
187 0     0 0 0 my ( $time_tested ) = @_;
188              
189 0         0 diag ' difference: ', $time_tested - call_m_result();
190              
191 0 0       0 $dumped++
192             and return;
193              
194 0         0 my $vers = Date::Manip->VERSION();
195              
196 0         0 diag '';
197              
198 0         0 diag "Date::Manip version: $vers";
199              
200 0         0 $vers =~ s/ _ //smxg;
201              
202 0 0       0 if ( $vers >= 6 ) {
203              
204 0         0 diag 'Date::Manip superclasses: ', join ', ', @Date::Manip::ISA;
205              
206 0 0       0 if ( Date::Manip->isa( 'Date::Manip::DM5' ) ) {
207 19     19   161 no warnings qw{ once };
  19         33  
  19         11970  
208 0         0 diag '$Cnf{Language}: ', $Date::Manip::DM5::Cnf{Language};
209             }
210              
211             }
212              
213 0 0       0 if ( my $code = Date::Manip->can( 'Date_TimeZone' ) ) {
214 0         0 diag 'Date_TimeZone = ', $code->();
215             } else {
216 0         0 diag 'Date_TimeZone unavailable';
217             }
218              
219 0 0       0 if ( $app->isa( 'Astro::App::Satpass2::ParseTime' ) ) {
220             # Only displays for Date::Manip v6 interface
221 0 0       0 $app->can( 'dmd_zone' )
222             and diag 'dmd_zone = ', $app->dmd_zone();
223 0 0       0 $app->can( '__epoch_offset' )
224             and diag 'epoch_offset = ', $app->__epoch_offset();
225             }
226              
227             {
228 0         0 local $ENV{DATE_MANIP_DEBUG} = 1;
  0         0  
229 0         0 local $@ = undef;
230 0         0 eval {
231 0         0 require Date::Manip::TZ;
232 0         0 my $text;
233 0         0 open my $fh, '>', \$text;
234 0         0 local *STDOUT = $fh;
235 0         0 Date::Manip::TZ->new();
236 0         0 close $fh;
237 0         0 diag $text;
238             };
239             }
240              
241 0         0 goto &__dump_zones;
242             }
243              
244             sub dump_date_manip_init {
245 0     0 0 0 $dumped = undef;
246 0         0 return;
247             }
248             }
249              
250             {
251             my $dumped;
252              
253             sub dump_zones {
254 0     0 0 0 my ( $time_tested ) = @_;
255              
256 0         0 diag ' difference: ', $time_tested - call_m_result();
257              
258 0 0       0 $dumped++
259             and return;
260              
261 0         0 goto &__dump_zones;
262             }
263              
264             sub dump_zones_init {
265 1     1 0 5 $dumped = undef;
266 1         3 return;
267             }
268             }
269              
270             sub __dump_zones {
271 0     0   0 my ( $time_tested ) = @_;
272              
273 0         0 local $@ = undef;
274 0 0       0 if ( eval { require DateTime; 1; } ) {
  0         0  
  0         0  
275 0         0 diag 'Have DateTime ', DateTime->VERSION();
276             } else {
277 0         0 diag 'DateTime not available';
278             }
279              
280 0 0       0 if ( eval { require DateTime::TimeZone; 1; } ) {
  0         0  
  0         0  
281 0         0 diag 'Have DateTime::TimeZone ', DateTime::TimeZone->VERSION();
282 0         0 my $dt_zone = DateTime::TimeZone->new( name => 'local')->name();
283 0         0 diag "DateTime::TimeZone is '$dt_zone'";
284             } else {
285 0         0 diag 'DateTime::TimeZone not available';
286             }
287              
288 0   0     0 diag strftime(
289             q, localtime( $time_tested || 0 ) );
290              
291 0         0 eval {
292 19     19   160 no strict qw{ refs };
  19         38  
  19         47681  
293 0 0       0 my $class = defined $Time::y2038::VERSION ? 'Time::y2038' :
294             'Time::Local';
295 0         0 diag sprintf 'Time to epoch uses %s %s', $class,
296             $class->VERSION();
297             };
298              
299 0 0       0 diag '$main::TZ is ', defined $main::TZ ? "'$main::TZ'" : 'undef';
300              
301 0 0       0 diag q<$ENV{TZ} = >, defined $ENV{TZ} ? "'$ENV{TZ}'" : 'undef';
302              
303 0         0 return;
304             }
305              
306             sub execute { ## no critic (RequireArgUnpacking)
307 232     232 1 221963 splice @_, 0, 0, 'execute';
308 232         951 goto &call_m;
309             }
310              
311             {
312             my $go;
313              
314             # CAVEAT:
315             #
316             # Unfortunately as things currently stand, the version needs to be
317             # maintained three places:
318             # - lib/Astro/App/Satpass2/Utils.pm
319             # - inc/My/Module/Recommend.pm
320             # - inc/My/Module/Test/App.pm
321             # These all need to stay the same. Sigh.
322             # Any such should be in xt/author/consistent_module_versions.t
323              
324             my %version = (
325             'DateTime::Calendar::Christian' => 0.06,
326             );
327              
328             # Expose the module version so we can test for consistent definition.
329             sub __module_version {
330 0     0   0 my $module = $_[-1];
331 0 0       0 exists $version{$module}
332             or confess "Bug - Module $module has no defined version";
333 0         0 return $version{$module};
334             }
335              
336             # skip() actually jumps out via 'goto SKIP', but Perl::Critic does
337             # not know this.
338             sub load_or_skip { ## no critic (RequireFinalReturn)
339 14     14 1 27407 my @arg = @_;
340 14   66     159 $go ||= Getopt::Long::Parser->new();
341 14         3805 my %opt;
342 14         109 $go->getoptionsfromarray( \@arg, \%opt,
343             qw{ import=s@ noimport! } );
344 14         9293 my ( $module, $skip ) = @arg;
345 14         47 my $v = $version{$module};
346 14         32 local $@ = undef;
347 14         54 my $caller = caller;
348             eval "require $module; 1"
349 14 100 66     1434 and eval {
350 8 50       50 $v and $module->VERSION( $v );
351 1         9 my @import = map { split qr{ \s* , \s* }smx }
352 8 100       20 @{ $opt{import} || [] };
  8         92  
353             # We rely on the following statement always being true
354             # unless the import is requested and fails.
355             $opt{noimport}
356 8 50       852 or eval "package $caller; $module->import( qw{ @import } ); 1";
357             } and return;
358 6 100       47 my $display = $v ? "$module $v" : $module;
359 6         15 $display .= ' not available';
360 6 100 66     55 $skip
361             and $skip =~ m/ \A all \z /smxi
362             and skip_all $display;
363 4         18 skip $display, $skip;
364             }
365             }
366              
367             {
368             my $got;
369              
370             sub call_m { ## no critic (RequireArgUnpacking)
371 408     408 1 133786 my ( $method, @args ) = @_;
372 408         1554 my ( $want, $title ) = splice @args, -2;
373              
374 408 100   408   3676 if ( defined( my $err = dies { $got = $app->$method( @args ) } ) ) {
  408         8079  
375 12         191 chomp $err;
376 12 50       36 defined $want or $want = 'Unexpected error';
377 12 50       399 REGEXP_REF eq ref $want
378             or $want = qr<\A\Q$want>smx;
379 12         56 @_ = ( $err, $want, $title );
380 12         115 goto &like;
381             } else {
382              
383 395 100       6890 if ( CODE_REF eq ref $want ) {
384 29         91 @_ = ( $want, $got, $title );
385 29         149 goto &$want;
386             }
387              
388 366         999 foreach ( $want, $got ) {
389 732 100 100     3155 defined and not ref and chomp;
390             }
391 366         1503 @_ = ( $got, $want, $title );
392 366 50       2879 REGEXP_REF eq ref $want ? goto &like : goto &is;
393             }
394             }
395              
396             sub call_m_result {
397 0     0 1 0 return $got;
398             }
399             }
400              
401             {
402             my $kind_hdr = {
403             configure_requires => 'CONFIGURE REQUIRES',
404             build_requires => 'BUILD REQUIRES',
405             test_requires => 'TEST REQUIRES',
406             requires => 'RUNTIME REQUIRES',
407             optional_modules => 'OPTIONAL MODULES',
408             };
409              
410             sub dependencies_table {
411 1     1 1 394370 require My::Module::Meta;
412 1         6 my @tables = ( '' );
413              
414             {
415 1         2 my @perls = ( My::Module::Meta->requires_perl(), $] );
  1         8  
416 1         5 foreach ( @perls ) {
417 2         13 $_ = sprintf '%.6f', $_;
418 2         12 $_ =~ s/ (?= ... \z ) /./smx;
419 2         15 $_ =~ s/ (?<= \. ) 00? //smxg;
420             }
421 1         12 push @tables, table(
422             header => [ qw{ PERL REQUIRED INSTALLED } ],
423             rows => [ [ perl => @perls ] ],
424             );
425             }
426              
427 1         2067 foreach my $kind ( qw{ configure_requires build_requires test_requires requires optional_modules }
428             ) {
429 5 100       46346 my $code = My::Module::Meta->can( $kind )
430             or next;
431 4         14 my $req = $code->();
432 4         10 my @rows;
433 4         6 foreach my $module ( sort keys %{ $req } ) {
  4         40  
434 59         223 ( my $file = "$module.pm" ) =~ s| :: |/|smxg;
435             # NOTE that an alternative implementation here is to use
436             # Module::Load::Conditional (core since 5.10.0) to find the
437             # installed modules, and then MM->parse_version() (from
438             # ExtUtils::MakeMaker) to find the version without actually
439             # loading the module.
440 59         79 my $installed;
441 59         91 local $@ = undef;
442 59 100       96 eval {
443 59         6510 require $file;
444 49         97548 $installed = $module->VERSION();
445 49 50       194 defined $installed
446             or $installed = 'undef';
447 49         104 1;
448             } or $installed = 'not installed';
449 59         214 push @rows, [ $module, $req->{$module}, $installed ];
450             }
451              
452 4         30 my $hdr = $kind_hdr->{$kind};
453 4 50       10 defined $hdr
454             or $hdr = uc $kind;
455 4         31 push @tables, table(
456             header => [ $hdr, 'REQUIRED', 'INSTALLED' ],
457             rows => \@rows,
458             );
459             }
460              
461 1         19730 return @tables;
462             }
463             }
464              
465             sub mock_usgs {
466 0     0 0 0 my ( $ele ) = @_;
467 0 0       0 my $attr = {
468             error => ( defined( $ele ) ?
469             'Unexpected error' :
470             'Forced error for testing' ),
471             };
472             return mock 'Geo::WebService::Elevation::USGS' => (
473             override => [
474             elevation => sub { return {
475 0     0   0 Elevation => $ele,
476             Units => 'Meters',
477             } },
478             get => sub {
479 0     0   0 my ( undef, $name ) = @_;
480 0 0       0 exists $attr->{$name}
481             or confess "Bug - Attribute '$name' not mocked";
482 0         0 return $attr->{$name};
483             },
484 0         0 ],
485             );
486             }
487              
488             {
489             my $win32 = sub {
490             my ( $path ) = @_;
491             $path =~ tr{\\}{/};
492             return $path;
493             };
494              
495             my %normalizer = (
496             dos => $win32,
497             dragonfly => sub {
498             my ( $path ) = @_;
499             $path =~ s{ / \z }{}smx;
500             return $path;
501             },
502             MSWin32 => $win32,
503             os2 => $win32,
504             );
505              
506             sub normalize_path {
507 4     4 1 13 my ( $path ) = @_;
508 4         198 $path = abs_path( $path );
509 4 50       77 my $code = $normalizer{$^O}
510             or return $path;
511 0         0 return $code->( $path );
512             }
513             }
514              
515             {
516              
517             my %no_stat = map { $_ => 1 } qw{ dos MSWin32 os2 riscos VMS };
518              
519             sub same_path {
520 2     2 1 20313 my ( $got, $want, $name ) = @_;
521 2         33 $got = normalize_path( $got );
522 2         41 $want = normalize_path( $want );
523 2 50 33     74 if ( $want eq $got || $no_stat{$^O} ) {
524 2         36 @_ = ( $got, $want, $name );
525 2         60 goto &is;
526             }
527 0         0 my $got_inode = ( stat $got )[1];
528 0         0 my $want_inode = ( stat $want )[1];
529 0         0 @_ = ( $got_inode, '==', $want_inode, $name );
530 0         0 goto &cmp_ok;
531             }
532             }
533              
534             sub setup_app_mocker {
535             return mock 'Astro::App::Satpass2' => (
536             override => [
537             new => sub {
538 4     4   2633 my ( $class, %self ) = @_;
539 4         20 return bless \%self, $class;
540             },
541 10     10   69 get => sub { return $_[0]{$_[1]}; },
542             set => sub {
543 0     0   0 my ( $self, @args ) = @_;
544 0         0 while ( @args ) {
545 0         0 my ( $name, $value ) = splice @args, 0, 2;
546 0         0 $self->{$name} = $value;
547             }
548 0         0 return $self;
549             },
550 4     4 1 1041542 ],
551             );
552             }
553              
554             sub Astro::App::Satpass2::__TEST__frame_stack_depth {
555 1     1   9 my ( $self ) = @_;
556 1         5 return scalar @{ $self->{frame} };
  1         72  
557             }
558              
559             sub Astro::App::Satpass2::__TEST__is_exported {
560 3     3   13 my ( $self, $name ) = @_;
561 3 100       20 return exists $self->{exported}{$name} ? 1 : 0;
562             }
563              
564             # $string = $self->__raw_attr( $name, $format )
565              
566             # Fetches the raw value of the named attribute, running it through
567             # the given sprintf format if that is not undef. THIS IS AN
568             # UNSUPPORTED INTERFACE USED FOR TESTING ONLY.
569              
570             sub Astro::App::Satpass2::__TEST__raw_attr {
571 3     3   12 my ( $self, $name, $format ) = @_;
572 3 50       24 defined $format or return $self->{$name};
573 3         56 return sprintf $format, $self->{$name};
574             }
575              
576             1;
577              
578             __END__