File Coverage

blib/lib/Test/API.pm
Criterion Covered Total %
statement 196 197 99.4
branch 43 48 89.5
condition 8 8 100.0
subroutine 42 42 100.0
pod 3 3 100.0
total 292 298 97.9


line stmt bran cond sub pod time code
1 3     3   241963 use 5.006;
  3         23  
2 3     3   15 use strict;
  3         5  
  3         62  
3 3     3   15 use warnings;
  3         4  
  3         145  
4              
5             package Test::API;
6             # ABSTRACT: Test a list of subroutines provided by a module
7              
8             our $VERSION = '0.010';
9              
10 3     3   19 use Symbol ();
  3         6  
  3         104  
11              
12 3     3   18 use Test::Builder::Module 0.86;
  3         54  
  3         18  
13             our @ISA = qw/Test::Builder::Module/;
14             our @EXPORT = qw/public_ok import_ok class_api_ok/;
15              
16             #--------------------------------------------------------------------------#
17              
18             sub import_ok ($;@) { ## no critic
19 11     11 1 24678 my $package = shift;
20 11         25 my %spec = @_;
21 11         46 for my $key (qw/export export_ok/) {
22 22   100     68 $spec{$key} ||= [];
23 22 100       48 $spec{$key} = [ $spec{$key} ] unless ref $spec{$key} eq 'ARRAY';
24             }
25 11         20 my $tb = _builder();
26 11         86 my @errors;
27             my %flagged;
28              
29 11         17 my $label = "importing from $package";
30              
31 11 100       17 return 0 unless _check_loaded( $package, $label );
32              
33             # test export
34             {
35 10         12 my $test_pkg = *{ Symbol::gensym() }{NAME};
  10         11  
  10         19  
36 10     1   820 eval "package $test_pkg; use $package;"; ## no critic
  1     1   7  
  1     1   2  
  1     1   16  
  1     1   8  
  1     1   9  
  1     1   14  
  1     1   7  
  1     1   1  
  1     1   35  
  1         7  
  1         3  
  1         27  
  1         6  
  1         2  
  1         37  
  1         9  
  1         2  
  1         32  
  1         6  
  1         2  
  1         31  
  1         8  
  1         2  
  1         31  
  1         7  
  1         2  
  1         31  
  1         6  
  1         1  
  1         39  
37 10         26 my ( $ok, $missing, $extra ) = _public_ok( $test_pkg, @{ $spec{export} } );
  10         28  
38 10 100       25 if ( !$ok ) {
39 4 100       10 push @errors, "not exported: @$missing" if @$missing;
40 4 100       8 @flagged{@$missing} = (1) x @$missing if @$missing;
41 4 100       11 push @errors, "unexpectedly exported: @$extra" if @$extra;
42 4 100       12 @flagged{@$extra} = (1) x @$extra if @$extra;
43             }
44             }
45              
46             # test export_ok
47 10         11 my @exportable;
48 10         15 for my $fcn ( _public_fcns($package) ) {
49 32 100       57 next if $flagged{$fcn}; # already complaining about this so skip
50 28 100       28 next if grep { $fcn eq $_ } @{ $spec{export} }; # exported by default
  45         81  
  28         37  
51 18         20 my $pkg_name = *{ Symbol::gensym() }{NAME};
  18         41  
52 18     1   1259 eval "package $pkg_name; use $package '$fcn';"; ## no critic
  1     1   6  
  1     1   2  
  1     1   40  
  1     1   5  
  1     1   2  
  1     1   26  
  1     1   6  
  1     1   1  
  1     1   245  
  1     1   7  
  1     1   1  
  1     1   27  
  1     1   8  
  1     1   3  
  1     1   50  
  1     1   6  
  1     1   2  
  1         129  
  1         6  
  1         2  
  1         25  
  1         5  
  1         2  
  1         34  
  1         5  
  1         2  
  1         144  
  1         6  
  1         2  
  1         24  
  1         6  
  1         1  
  1         38  
  1         13  
  1         3  
  1         148  
  1         6  
  1         21  
  1         31  
  1         6  
  1         1  
  1         25  
  1         6  
  1         17  
  1         153  
  1         6  
  1         2  
  1         27  
  1         6  
  1         2  
  1         26  
  1         6  
  1         2  
  1         132  
53 18         58 my ( $ok, $missing, $extra ) = _public_ok( $pkg_name, $fcn );
54 18 100       46 if ($ok) {
55 12         25 push @exportable, $fcn;
56             }
57             }
58 10         26 my ( $missing, $extra ) = _difference( $spec{export_ok}, \@exportable, );
59 10 100       24 push @errors, "not optionally exportable: @$missing" if @$missing;
60 10 100       19 push @errors, "extra optionally exportable: @$extra" if @$extra;
61              
62             # notify of results
63 10         40 $tb->ok( !@errors, "importing from $package" );
64 10         5712 $tb->diag($_) for @errors;
65 10         1280 return !@errors;
66             }
67              
68             #--------------------------------------------------------------------------#
69              
70             sub public_ok ($;@) { ## no critic
71 8     8 1 20572 my ( $package, @expected ) = @_;
72 8         19 my $tb = _builder();
73 8         65 my $label = "public API for $package";
74              
75 8 100       17 return 0 unless _check_loaded( $package, $label );
76              
77 7         15 my ( $ok, $missing, $extra ) = _public_ok( $package, @expected );
78 7         25 $tb->ok( $ok, $label );
79 7 100       3349 if ( !$ok ) {
80 3 100       14 $tb->diag("missing: @$missing") if @$missing;
81 3 100       463 $tb->diag("extra: @$extra") if @$extra;
82             }
83 7         493 return $ok;
84             }
85              
86             #--------------------------------------------------------------------------#
87              
88             sub class_api_ok ($;@) { ## no critic
89 3     3 1 7608 my ( $package, @expected ) = @_;
90 3         8 my $tb = _builder();
91 3         30 my $label = "public API for class $package";
92              
93 3 50       9 return 0 unless _check_loaded( $package, $label );
94              
95 3         8 my ( $ok, $missing, $extra ) = _public_ok( $package, @expected );
96              
97             # Call ->can to check if missing methods might be provided
98             # by parent classes...
99 3 50       7 if ( !$ok ) {
100 3         4 @$missing = grep { not $package->can($_) } @$missing;
  5         27  
101 3         4 $ok = not( scalar(@$missing) + scalar(@$extra) );
102             }
103              
104 3         10 $tb->ok( $ok, $label );
105 3 100       1342 if ( !$ok ) {
106 1 50       5 $tb->diag("missing: @$missing") if @$missing;
107 1 50       6 $tb->diag("extra: @$extra") if @$extra;
108             }
109 3         200 return $ok;
110             }
111              
112             #--------------------------------------------------------------------------#
113              
114             sub _builder {
115 24     24   85 return __PACKAGE__->builder;
116             }
117              
118             #--------------------------------------------------------------------------#
119              
120             sub _check_loaded {
121 22     22   43 my ( $package, $label ) = @_;
122 22         79 ( my $path = $package ) =~ s{::}{/}g;
123 22         59 $path .= ".pm";
124 22 100       55 if ( $INC{$path} ) {
125 20         54 return 1;
126             }
127             else {
128 2         5 my $tb = _builder();
129 2         14 local $Test::Builder::Level = $Test::Builder::Level + 1;
130 2         7 $tb->ok( 0, $label );
131 2         1837 $tb->diag("Module '$package' not loaded");
132 2         400 return;
133             }
134             }
135              
136             #--------------------------------------------------------------------------#
137              
138             sub _difference {
139 48     48   71 my ( $array1, $array2 ) = @_;
140 48         58 my ( %only1, %only2 );
141 48         100 @only1{@$array1} = (1) x @$array1;
142 48         76 delete @only1{@$array2};
143 48         70 @only2{@$array2} = (1) x @$array2;
144 48         63 delete @only2{@$array1};
145 48         164 return ( [ sort keys %only1 ], [ sort keys %only2 ] );
146             }
147              
148             #--------------------------------------------------------------------------#
149              
150             # list adapted from Pod::Coverage
151             my %private = map { ; $_ => 1 } qw(
152             import unimport bootstrap
153              
154             AUTOLOAD BUILD BUILDARGS CLONE CLONE_SKIP DESTROY DEMOLISH meta
155              
156             TIESCALAR TIEARRAY TIEHASH TIEHANDLE
157              
158             FETCH STORE UNTIE FETCHSIZE STORESIZE POP PUSH SHIFT UNSHIFT SPLICE
159             DELETE EXISTS EXTEND CLEAR FIRSTKEY NEXTKEY PRINT PRINTF WRITE
160             READLINE GETC READ CLOSE BINMODE OPEN EOF FILENO SEEK TELL SCALAR
161              
162             MODIFY_REF_ATTRIBUTES MODIFY_SCALAR_ATTRIBUTES MODIFY_ARRAY_ATTRIBUTES
163             MODIFY_HASH_ATTRIBUTES MODIFY_CODE_ATTRIBUTES MODIFY_GLOB_ATTRIBUTES
164             MODIFY_FORMAT_ATTRIBUTES MODIFY_IO_ATTRIBUTES
165              
166             FETCH_REF_ATTRIBUTES FETCH_SCALAR_ATTRIBUTES FETCH_ARRAY_ATTRIBUTES
167             FETCH_HASH_ATTRIBUTES FETCH_CODE_ATTRIBUTES FETCH_GLOB_ATTRIBUTES
168             FETCH_FORMAT_ATTRIBUTES FETCH_IO_ATTRIBUTES
169             );
170              
171             sub _public_fcns {
172 48     48   74 my ($package) = @_;
173 3     3   3238 no strict qw(refs);
  3         7  
  3         920  
174 48         51 my $stash = \%{"$package\::"};
  48         120  
175 48         62 my @syms;
176 48         127 for (keys %$stash) {
177             push @syms,
178             ref \$stash->{$_} eq 'GLOB'
179             ? \$stash->{$_}
180 164 50       335 : \*{"$package:\:$_"}
  0         0  
181             }
182 75 100 100     395 return grep { substr( $_, 0, 1 ) ne '_' && !$private{$_} && $_ !~ /^\(/ }
183 75         705 map { ( my $f = *$_ ) =~ s/^\*$package\:://; $f }
  75         188  
184 48         84 grep { defined( *$_{CODE} ) } @syms;
  164         291  
185             }
186              
187             #--------------------------------------------------------------------------#
188              
189             sub _public_ok ($;@) { ## no critic
190 38     38   92 my ( $package, @expected ) = @_;
191 38         65 my @fcns = _public_fcns($package);
192 38         84 my ( $missing, $extra ) = _difference( \@expected, \@fcns );
193 38   100     199 return ( !@$missing && !@$extra, $missing, $extra );
194             }
195              
196             1;
197              
198             __END__