File Coverage

blib/lib/Test/API.pm
Criterion Covered Total %
statement 192 192 100.0
branch 42 46 91.3
condition 8 8 100.0
subroutine 42 42 100.0
pod 3 3 100.0
total 287 291 98.6


line stmt bran cond sub pod time code
1 3     3   86697 use 5.006;
  3         8  
2 3     3   13 use strict;
  3         4  
  3         70  
3 3     3   16 use warnings;
  3         3  
  3         186  
4              
5             package Test::API;
6             # ABSTRACT: Test a list of subroutines provided by a module
7              
8             our $VERSION = '0.008';
9              
10 3     3   13 use Symbol ();
  3         5  
  3         111  
11              
12 3     3   15 use Test::Builder::Module 0.86;
  3         53  
  3         20  
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 8884 my $package = shift;
20 11         21 my %spec = @_;
21 11         16 for my $key (qw/export export_ok/) {
22 22   100     59 $spec{$key} ||= [];
23 22 100       46 $spec{$key} = [ $spec{$key} ] unless ref $spec{$key} eq 'ARRAY';
24             }
25 11         22 my $tb = _builder();
26 11         54 my @errors;
27             my %flagged;
28              
29 11         14 my $label = "importing from $package";
30              
31 11 100       19 return 0 unless _check_loaded( $package, $label );
32              
33             # test export
34             {
35 10         9 my $test_pkg = *{ Symbol::gensym() }{NAME};
  10         7  
  10         18  
36 10     1   776 eval "package $test_pkg; use $package;"; ## no critic
  1     1   6  
  1     1   1  
  1     1   16  
  1     1   4  
  1     1   1  
  1     1   11  
  1     1   4  
  1     1   1  
  1     1   38  
  1         6  
  1         2  
  1         49  
  1         7  
  1         2  
  1         52  
  1         4  
  1         2  
  1         28  
  1         4  
  1         1  
  1         31  
  1         4  
  1         1  
  1         30  
  1         4  
  1         1  
  1         54  
  1         6  
  1         1  
  1         51  
37 10         14 my ( $ok, $missing, $extra ) = _public_ok( $test_pkg, @{ $spec{export} } );
  10         26  
38 10 100       22 if ( !$ok ) {
39 4 100       12 push @errors, "not exported: @$missing" if @$missing;
40 4 100       9 @flagged{@$missing} = (1) x @$missing if @$missing;
41 4 100       13 push @errors, "unexpectedly exported: @$extra" if @$extra;
42 4 100       13 @flagged{@$extra} = (1) x @$extra if @$extra;
43             }
44             }
45              
46             # test export_ok
47 10         12 my @exportable;
48 10         11 for my $fcn ( _public_fcns($package) ) {
49 32 100       51 next if $flagged{$fcn}; # already complaining about this so skip
50 28 100       20 next if grep { $fcn eq $_ } @{ $spec{export} }; # exported by default
  45         74  
  28         33  
51 18         28 my $pkg_name = *{ Symbol::gensym() }{NAME};
  18         36  
52 18     1   1236 eval "package $pkg_name; use $package '$fcn';"; ## no critic
  1     1   5  
  1     1   1  
  1     1   31  
  1     1   4  
  1     1   1  
  1     1   24  
  1     1   3  
  1     1   2  
  1     1   224  
  1     1   4  
  1     1   1  
  1     1   25  
  1     1   4  
  1     1   1  
  1     1   26  
  1     1   6  
  1     1   1  
  1         147  
  1         3  
  1         2  
  1         24  
  1         4  
  1         1  
  1         23  
  1         4  
  1         2  
  1         173  
  1         4  
  1         2  
  1         24  
  1         4  
  1         1  
  1         27  
  1         4  
  1         1  
  1         147  
  1         4  
  1         1  
  1         24  
  1         4  
  1         1  
  1         24  
  1         4  
  1         1  
  1         143  
  1         5  
  1         2  
  1         34  
  1         6  
  1         2  
  1         38  
  1         8  
  1         2  
  1         234  
53 18         34 my ( $ok, $missing, $extra ) = _public_ok( $pkg_name, $fcn );
54 18 100       34 if ($ok) {
55 12         21 push @exportable, $fcn;
56             }
57             }
58 10         24 my ( $missing, $extra ) = _difference( $spec{export_ok}, \@exportable, );
59 10 100       24 push @errors, "not optionally exportable: @$missing" if @$missing;
60 10 100       45 push @errors, "extra optionally exportable: @$extra" if @$extra;
61              
62             # notify of results
63 10         39 $tb->ok( !@errors, "importing from $package" );
64 10         3003 $tb->diag($_) for @errors;
65 10         328 return !@errors;
66             }
67              
68             #--------------------------------------------------------------------------#
69              
70             sub public_ok ($;@) { ## no critic
71 7     7 1 9807 my ( $package, @expected ) = @_;
72 7         16 my $tb = _builder();
73 7         53 my $label = "public API for $package";
74              
75 7 100       13 return 0 unless _check_loaded( $package, $label );
76              
77 6         16 my ( $ok, $missing, $extra ) = _public_ok( $package, @expected );
78 6         21 $tb->ok( $ok, $label );
79 6 100       2331 if ( !$ok ) {
80 3 100       49 $tb->diag("missing: @$missing") if @$missing;
81 3 100       163 $tb->diag("extra: @$extra") if @$extra;
82             }
83 6         264 return $ok;
84             }
85              
86             #--------------------------------------------------------------------------#
87              
88             sub class_api_ok ($;@) { ## no critic
89 3     3 1 3258 my ( $package, @expected ) = @_;
90 3         6 my $tb = _builder();
91 3         24 my $label = "public API for class $package";
92              
93 3 50       8 return 0 unless _check_loaded( $package, $label );
94              
95 3         10 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       6 if ( !$ok ) {
100 3         4 @$missing = grep { not $package->can($_) } @$missing;
  5         28  
101 3         4 $ok = not( scalar(@$missing) + scalar(@$extra) );
102             }
103              
104 3         7 $tb->ok( $ok, $label );
105 3 100       737 if ( !$ok ) {
106 1 50       4 $tb->diag("missing: @$missing") if @$missing;
107 1 50       5 $tb->diag("extra: @$extra") if @$extra;
108             }
109 3         59 return $ok;
110             }
111              
112             #--------------------------------------------------------------------------#
113              
114             sub _builder {
115 23     23   82 return __PACKAGE__->builder;
116             }
117              
118             #--------------------------------------------------------------------------#
119              
120             sub _check_loaded {
121 21     21   22 my ( $package, $label ) = @_;
122 21         95 ( my $path = $package ) =~ s{::}{/}g;
123 21         25 $path .= ".pm";
124 21 100       53 if ( $INC{$path} ) {
125 19         52 return 1;
126             }
127             else {
128 2         5 my $tb = _builder();
129 2         13 local $Test::Builder::Level = $Test::Builder::Level + 1;
130 2         7 $tb->ok( 0, $label );
131 2         888 $tb->diag("Module '$package' not loaded");
132 2         121 return;
133             }
134             }
135              
136             #--------------------------------------------------------------------------#
137              
138             sub _difference {
139 47     47   44 my ( $array1, $array2 ) = @_;
140 47         36 my ( %only1, %only2 );
141 47         90 @only1{@$array1} = (1) x @$array1;
142 47         59 delete @only1{@$array2};
143 47         60 @only2{@$array2} = (1) x @$array2;
144 47         67 delete @only2{@$array1};
145 47         167 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 47     47   44 my ($package) = @_;
173 3     3   3856 no strict qw(refs);
  3         4  
  3         717  
174 74 100 100     472 return grep { substr( $_, 0, 1 ) ne '_' && !$private{$_} && $_ !~ /^\(/ }
175 74         769 map { ( my $f = $_ ) =~ s/^\*$package\:://; $f }
  74         166  
176 47         43 grep { defined( *$_{CODE} ) } values( %{"$package\::"} );
  162         253  
  47         127  
177             }
178              
179             #--------------------------------------------------------------------------#
180              
181             sub _public_ok ($;@) { ## no critic
182 37     37   64 my ( $package, @expected ) = @_;
183 37         53 my @fcns = _public_fcns($package);
184 37         78 my ( $missing, $extra ) = _difference( \@expected, \@fcns );
185 37   100     173 return ( !@$missing && !@$extra, $missing, $extra );
186             }
187              
188             1;
189              
190             __END__