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   223903 use 5.006;
  3         24  
2 3     3   18 use strict;
  3         6  
  3         78  
3 3     3   20 use warnings;
  3         5  
  3         159  
4              
5             package Test::API;
6             # ABSTRACT: Test a list of subroutines provided by a module
7              
8             our $VERSION = '0.009'; # TRIAL
9              
10 3     3   19 use Symbol ();
  3         5  
  3         106  
11              
12 3     3   18 use Test::Builder::Module 0.86;
  3         58  
  3         21  
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 32656 my $package = shift;
20 11         43 my %spec = @_;
21 11         28 for my $key (qw/export export_ok/) {
22 22   100     89 $spec{$key} ||= [];
23 22 100       80 $spec{$key} = [ $spec{$key} ] unless ref $spec{$key} eq 'ARRAY';
24             }
25 11         37 my $tb = _builder();
26 11         144 my @errors;
27             my %flagged;
28              
29 11         29 my $label = "importing from $package";
30              
31 11 100       31 return 0 unless _check_loaded( $package, $label );
32              
33             # test export
34             {
35 10         20 my $test_pkg = *{ Symbol::gensym() }{NAME};
  10         23  
  10         37  
36 10     1   1398 eval "package $test_pkg; use $package;"; ## no critic
  1     1   10  
  1     1   3  
  1     1   24  
  1     1   9  
  1     1   2  
  1     1   27  
  1     1   10  
  1     1   3  
  1     1   63  
  1         8  
  1         2  
  1         43  
  1         12  
  1         3  
  1         55  
  1         8  
  1         2  
  1         55  
  1         9  
  1         3  
  1         46  
  1         10  
  1         2  
  1         56  
  1         10  
  1         2  
  1         73  
  1         9  
  1         3  
  1         45  
37 10         29 my ( $ok, $missing, $extra ) = _public_ok( $test_pkg, @{ $spec{export} } );
  10         45  
38 10 100       33 if ( !$ok ) {
39 4 100       16 push @errors, "not exported: @$missing" if @$missing;
40 4 100       13 @flagged{@$missing} = (1) x @$missing if @$missing;
41 4 100       18 push @errors, "unexpectedly exported: @$extra" if @$extra;
42 4 100       14 @flagged{@$extra} = (1) x @$extra if @$extra;
43             }
44             }
45              
46             # test export_ok
47 10         17 my @exportable;
48 10         22 for my $fcn ( _public_fcns($package) ) {
49 32 100       78 next if $flagged{$fcn}; # already complaining about this so skip
50 28 100       39 next if grep { $fcn eq $_ } @{ $spec{export} }; # exported by default
  45         118  
  28         62  
51 18         28 my $pkg_name = *{ Symbol::gensym() }{NAME};
  18         56  
52 18     1   1674 eval "package $pkg_name; use $package '$fcn';"; ## no critic
  1     1   8  
  1     1   2  
  1     1   40  
  1     1   7  
  1     1   1  
  1     1   345  
  1     1   7  
  1     1   2  
  1     1   31  
  1     1   9  
  1     1   2  
  1     1   34  
  1     1   7  
  1     1   1  
  1     1   219  
  1     1   7  
  1     1   2  
  1         30  
  1         7  
  1         2  
  1         33  
  1         7  
  1         2  
  1         307  
  1         7  
  1         2  
  1         32  
  1         7  
  1         2  
  1         45  
  1         7  
  1         3  
  1         273  
  1         8  
  1         2  
  1         57  
  1         7  
  1         32  
  1         41  
  1         7  
  1         2  
  1         270  
  1         7  
  1         25  
  1         38  
  1         7  
  1         3  
  1         33  
  1         7  
  1         2  
  1         224  
  1         8  
  1         2  
  1         32  
53 18         75 my ( $ok, $missing, $extra ) = _public_ok( $pkg_name, $fcn );
54 18 100       55 if ($ok) {
55 12         36 push @exportable, $fcn;
56             }
57             }
58 10         35 my ( $missing, $extra ) = _difference( $spec{export_ok}, \@exportable, );
59 10 100       34 push @errors, "not optionally exportable: @$missing" if @$missing;
60 10 100       39 push @errors, "extra optionally exportable: @$extra" if @$extra;
61              
62             # notify of results
63 10         72 $tb->ok( !@errors, "importing from $package" );
64 10         8356 $tb->diag($_) for @errors;
65 10         1522 return !@errors;
66             }
67              
68             #--------------------------------------------------------------------------#
69              
70             sub public_ok ($;@) { ## no critic
71 8     8 1 22481 my ( $package, @expected ) = @_;
72 8         18 my $tb = _builder();
73 8         71 my $label = "public API for $package";
74              
75 8 100       20 return 0 unless _check_loaded( $package, $label );
76              
77 7         20 my ( $ok, $missing, $extra ) = _public_ok( $package, @expected );
78 7         28 $tb->ok( $ok, $label );
79 7 100       3789 if ( !$ok ) {
80 3 100       16 $tb->diag("missing: @$missing") if @$missing;
81 3 100       569 $tb->diag("extra: @$extra") if @$extra;
82             }
83 7         515 return $ok;
84             }
85              
86             #--------------------------------------------------------------------------#
87              
88             sub class_api_ok ($;@) { ## no critic
89 3     3 1 8821 my ( $package, @expected ) = @_;
90 3         7 my $tb = _builder();
91 3         29 my $label = "public API for class $package";
92              
93 3 50       7 return 0 unless _check_loaded( $package, $label );
94              
95 3         9 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         6 @$missing = grep { not $package->can($_) } @$missing;
  5         29  
101 3         8 $ok = not( scalar(@$missing) + scalar(@$extra) );
102             }
103              
104 3         10 $tb->ok( $ok, $label );
105 3 100       1507 if ( !$ok ) {
106 1 50       5 $tb->diag("missing: @$missing") if @$missing;
107 1 50       7 $tb->diag("extra: @$extra") if @$extra;
108             }
109 3         242 return $ok;
110             }
111              
112             #--------------------------------------------------------------------------#
113              
114             sub _builder {
115 24     24   106 return __PACKAGE__->builder;
116             }
117              
118             #--------------------------------------------------------------------------#
119              
120             sub _check_loaded {
121 22     22   55 my ( $package, $label ) = @_;
122 22         112 ( my $path = $package ) =~ s{::}{/}g;
123 22         69 $path .= ".pm";
124 22 100       69 if ( $INC{$path} ) {
125 20         77 return 1;
126             }
127             else {
128 2         6 my $tb = _builder();
129 2         19 local $Test::Builder::Level = $Test::Builder::Level + 1;
130 2         10 $tb->ok( 0, $label );
131 2         2206 $tb->diag("Module '$package' not loaded");
132 2         479 return;
133             }
134             }
135              
136             #--------------------------------------------------------------------------#
137              
138             sub _difference {
139 48     48   86 my ( $array1, $array2 ) = @_;
140 48         71 my ( %only1, %only2 );
141 48         126 @only1{@$array1} = (1) x @$array1;
142 48         98 delete @only1{@$array2};
143 48         86 @only2{@$array2} = (1) x @$array2;
144 48         80 delete @only2{@$array1};
145 48         220 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   84 my ($package) = @_;
173 3     3   3663 no strict qw(refs);
  3         8  
  3         928  
174 48         71 my $stash = \%{"$package\::"};
  48         186  
175 48         79 my @syms;
176 48         140 for (keys %$stash) {
177             push @syms,
178             ref \$stash->{$_} eq 'GLOB'
179             ? \$stash->{$_}
180 164 50       479 : \*{"$package:\:$_"}
  0         0  
181             }
182 75 100 100     541 return grep { substr( $_, 0, 1 ) ne '_' && !$private{$_} && $_ !~ /^\(/ }
183 75         954 map { ( my $f = *$_ ) =~ s/^\*$package\:://; $f }
  75         248  
184 48         105 grep { defined( *$_{CODE} ) } @syms;
  164         357  
185             }
186              
187             #--------------------------------------------------------------------------#
188              
189             sub _public_ok ($;@) { ## no critic
190 38     38   103 my ( $package, @expected ) = @_;
191 38         84 my @fcns = _public_fcns($package);
192 38         110 my ( $missing, $extra ) = _difference( \@expected, \@fcns );
193 38   100     225 return ( !@$missing && !@$extra, $missing, $extra );
194             }
195              
196             1;
197              
198             __END__