| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Test::Pod::Coverage::Permissive; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 8 |  |  | 8 |  | 248810 | use warnings; | 
|  | 8 |  |  |  |  | 22 |  | 
|  | 8 |  |  |  |  | 321 |  | 
| 4 | 8 |  |  | 8 |  | 51 | use strict; | 
|  | 8 |  |  |  |  | 15 |  | 
|  | 8 |  |  |  |  | 276 |  | 
| 5 | 8 |  |  | 8 |  | 242 | use 5.008009; | 
|  | 8 |  |  |  |  | 45 |  | 
|  | 8 |  |  |  |  | 390 |  | 
| 6 | 8 |  |  | 8 |  | 48 | use Test::More 0.88; | 
|  | 8 |  |  |  |  | 250 |  | 
|  | 8 |  |  |  |  | 60 |  | 
| 7 | 8 |  |  | 8 |  | 2534 | use File::Spec; | 
|  | 8 |  |  |  |  | 27 |  | 
|  | 8 |  |  |  |  | 238 |  | 
| 8 | 8 |  |  | 8 |  | 55 | use Pod::Coverage; | 
|  | 8 |  |  |  |  | 22 |  | 
|  | 8 |  |  |  |  | 301 |  | 
| 9 | 8 |  |  | 8 |  | 11295 | use YAML::Syck qw(LoadFile DumpFile); | 
|  | 8 |  |  |  |  | 27631 |  | 
|  | 8 |  |  |  |  | 957 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | my $Test = Test::Builder->new; | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | sub import { | 
| 14 | 8 |  |  | 8 |  | 174 | my $self = shift; | 
| 15 | 8 |  |  |  |  | 22 | my $caller = caller; | 
| 16 | 8 |  |  | 8 |  | 71 | no strict 'refs'; | 
|  | 8 |  |  |  |  | 20 |  | 
|  | 8 |  |  |  |  | 9240 |  | 
| 17 | 8 |  |  |  |  | 20 | *{$caller.'::pod_coverage_ok'}       = \&pod_coverage_ok; | 
|  | 8 |  |  |  |  | 51 |  | 
| 18 | 8 |  |  |  |  | 16 | *{$caller.'::all_pod_coverage_ok'}   = \&all_pod_coverage_ok; | 
|  | 8 |  |  |  |  | 43 |  | 
| 19 | 8 |  |  |  |  | 19 | *{$caller.'::all_modules'}           = \&all_modules; | 
|  | 8 |  |  |  |  | 38 |  | 
| 20 |  |  |  |  |  |  |  | 
| 21 | 8 |  |  |  |  | 50 | $Test->exported_to($caller); | 
| 22 | 8 |  |  |  |  | 108 | $Test->plan(@_); | 
| 23 |  |  |  |  |  |  | } | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | =head1 NAME | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | Test::Pod::Coverage::Permissive - Checks for pod coverage regression. | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  | =head1 VERSION | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | Version 0.05 | 
| 32 |  |  |  |  |  |  |  | 
| 33 |  |  |  |  |  |  | =cut | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | our $VERSION = '0.05'; | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | Checks for POD coverage regressions in your code. This module is for large projects, which can't be covered by POD for a | 
| 40 |  |  |  |  |  |  | 5 minutes. If you have small module or your project is fully covered - use L instead. | 
| 41 |  |  |  |  |  |  |  | 
| 42 |  |  |  |  |  |  | After first run, this module creates data file, where saves all uncovered subroutines. If you create new uncovered | 
| 43 |  |  |  |  |  |  | subroutine, it will fail. If you create new package with uncovered subroutines, it will fail. Otherwise it will show | 
| 44 |  |  |  |  |  |  | diagnostic messages like these: | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | t/03podcoverage.t .. 2/? # YourProject::Controller::Root: naked 4 subroutine(s) | 
| 47 |  |  |  |  |  |  | # YourProject::Controller::NotRoot: naked 8 subroutine(s) | 
| 48 |  |  |  |  |  |  | # YorProject::Controller::AlsoNotRoot: naked 3 subroutine(s) | 
| 49 |  |  |  |  |  |  | ... | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | This module will help you to cover your project step-by-step. And your new code will be covered by POD. | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | Interface is like L: | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | use Test::Pod::Coverage::Permissive; | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | use Test::More; | 
| 58 |  |  |  |  |  |  | eval "use Test::Pod::Coverage::Permissive"; | 
| 59 |  |  |  |  |  |  | plan skip_all => "Test::Pod::Coverage::Permissive required for testing POD coverage" if $@; | 
| 60 |  |  |  |  |  |  | all_pod_coverage_ok(); | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | =head2 all_pod_coverage_ok( [$parms] ) | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | Checks that the POD code in all modules in the distro have proper POD | 
| 67 |  |  |  |  |  |  | coverage. | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | If the I<$parms> hashref if passed in, they're passed into the | 
| 70 |  |  |  |  |  |  | C object that the function uses.  Check the | 
| 71 |  |  |  |  |  |  | L manual for what those can be. | 
| 72 |  |  |  |  |  |  |  | 
| 73 |  |  |  |  |  |  | The exception is the C parameter, which specifies a class to | 
| 74 |  |  |  |  |  |  | use for coverage testing.  It defaults to C. | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | =cut | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | sub all_pod_coverage_ok { | 
| 79 | 1 | 50 | 33 | 1 | 1 | 14 | my $parms = ( @_ && ( ref $_[0] eq "HASH" ) ) ? shift : {}; | 
| 80 | 1 |  |  |  |  | 2 | my $msg = shift; | 
| 81 |  |  |  |  |  |  |  | 
| 82 | 1 |  |  |  |  | 2 | my $ok         = 1; | 
| 83 | 1 |  |  |  |  | 5 | my @modules    = all_modules(); | 
| 84 | 1 | 50 |  |  |  | 4 | if (@modules) { | 
| 85 | 1 |  |  |  |  | 3 | for my $module (@modules) { | 
| 86 | 1 |  |  |  |  | 4 | pod_coverage_ok($module, $parms, $msg); | 
| 87 |  |  |  |  |  |  | } | 
| 88 |  |  |  |  |  |  | } | 
| 89 |  |  |  |  |  |  | else { | 
| 90 | 0 |  |  |  |  | 0 | ok( 1, "No modules found." ); | 
| 91 |  |  |  |  |  |  | } | 
| 92 |  |  |  |  |  |  |  | 
| 93 | 1 |  |  |  |  | 352 | return $ok; | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  |  | 
| 96 |  |  |  |  |  |  | =head2 pod_coverage_ok( $module, [$parms,] $msg ) | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | Checks that the POD code in I<$module> has proper POD coverage. | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | If the I<$parms> hashref if passed in, they're passed into the | 
| 101 |  |  |  |  |  |  | C object that the function uses.  Check the | 
| 102 |  |  |  |  |  |  | L manual for what those can be. | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | The exception is the C parameter, which specifies a class to | 
| 105 |  |  |  |  |  |  | use for coverage testing.  It defaults to C. | 
| 106 |  |  |  |  |  |  |  | 
| 107 |  |  |  |  |  |  | =cut | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | sub pod_coverage_ok { | 
| 110 | 11 |  |  | 11 | 1 | 7176 | my $module = shift; | 
| 111 | 11 | 100 | 100 |  |  | 113 | my %parms = (@_ && (ref $_[0] eq "HASH")) ? %{(shift)} : (); | 
|  | 3 |  |  |  |  | 15 |  | 
| 112 | 11 | 100 |  |  |  | 64 | my $msg = @_ ? shift : "Pod coverage on $module"; | 
| 113 | 11 |  |  |  |  | 284 | my $first_time = !-e 't/pod_correct.yaml'; | 
| 114 | 11 |  | 50 |  |  | 25 | my $correct = eval { LoadFile('t/pod_correct.yaml') } || {}; | 
| 115 | 11 |  |  |  |  | 2996 | my $coverage = Pod::Coverage->new( package => $module, %parms ); | 
| 116 | 11 |  | 100 |  |  | 19175 | my $v = $coverage->naked || 0; | 
| 117 | 11 |  |  |  |  | 4632 | my $ok = 1; | 
| 118 | 11 | 100 |  |  |  | 14628 | if ( defined $coverage->coverage ) { | 
| 119 | 8 | 50 |  |  |  | 2605 | $correct->{$module} = $v if $first_time; | 
| 120 | 8 | 100 | 50 |  |  | 115 | if ( $ok = $Test->ok($v <= ($correct->{$module}||0), $msg) ) { | 
| 121 | 7 |  |  |  |  | 2932 | $correct->{$module} = $v; | 
| 122 |  |  |  |  |  |  | } | 
| 123 | 8 | 100 |  |  |  | 863 | if ( my $count = $coverage->naked ) { | 
| 124 | 1 |  |  |  |  | 7 | $Test->diag("${module}: naked $count subroutine(s)"); | 
| 125 |  |  |  |  |  |  | } | 
| 126 |  |  |  |  |  |  | } | 
| 127 |  |  |  |  |  |  | else { # No symbols | 
| 128 | 3 |  |  |  |  | 115 | my $why = $coverage->why_unrated; | 
| 129 | 3 |  |  |  |  | 11 | my $nopublics = ( $why =~ "no public symbols defined" ); | 
| 130 | 3 |  | 100 |  |  | 20 | my $verbose = $ENV{HARNESS_VERBOSE} || 0; | 
| 131 | 3 | 50 |  |  |  | 10 | $correct->{$module} = undef if $first_time; | 
| 132 | 3 |  | 66 |  |  | 17 | $ok = $nopublics || exists $coverage->{$module}; | 
| 133 | 3 |  |  |  |  | 19 | $Test->ok( $ok, $msg ); | 
| 134 | 3 | 100 | 100 |  |  | 1322 | $Test->diag( "$module: $why" ) unless ( $nopublics && !$verbose ); | 
| 135 |  |  |  |  |  |  | } | 
| 136 |  |  |  |  |  |  |  | 
| 137 | 11 |  |  |  |  | 361 | DumpFile( 't/pod_correct.yaml', $correct ); | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | =head2 all_modules( [@dirs] ) | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | Returns a list of all modules in I<$dir> and in directories below. If | 
| 143 |  |  |  |  |  |  | no directories are passed, it defaults to F if F exists, | 
| 144 |  |  |  |  |  |  | or F if not. | 
| 145 |  |  |  |  |  |  |  | 
| 146 |  |  |  |  |  |  | Note that the modules are as "Foo::Bar", not "Foo/Bar.pm". | 
| 147 |  |  |  |  |  |  |  | 
| 148 |  |  |  |  |  |  | The order of the files returned is machine-dependent.  If you want them | 
| 149 |  |  |  |  |  |  | sorted, you'll have to sort them yourself. | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | =cut | 
| 152 |  |  |  |  |  |  |  | 
| 153 |  |  |  |  |  |  | sub all_modules { | 
| 154 | 2 | 100 |  | 2 | 1 | 23 | my @starters = @_ ? @_ : _starting_points(); | 
| 155 | 2 |  |  |  |  | 9 | my %starters = map { $_, 1 } @starters; | 
|  | 2 |  |  |  |  | 13 |  | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 2 |  |  |  |  | 7 | my @queue = @starters; | 
| 158 |  |  |  |  |  |  |  | 
| 159 | 2 |  |  |  |  | 4 | my @modules; | 
| 160 | 2 |  |  |  |  | 11 | while (@queue) { | 
| 161 | 58 |  |  |  |  | 150 | my $file = shift @queue; | 
| 162 | 58 | 100 |  |  |  | 994 | if ( -d $file ) { | 
| 163 | 40 |  |  |  |  | 104 | local *DH; | 
| 164 | 40 | 50 |  |  |  | 973 | opendir DH, $file or next; | 
| 165 | 40 |  |  |  |  | 558 | my @newfiles = readdir DH; | 
| 166 | 40 |  |  |  |  | 446 | closedir DH; | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 40 |  |  |  |  | 645 | @newfiles = File::Spec->no_upwards(@newfiles); | 
| 169 | 40 | 50 |  |  |  | 90 | @newfiles = grep { $_ ne "CVS" && $_ ne ".svn" } @newfiles; | 
|  | 56 |  |  |  |  | 428 |  | 
| 170 |  |  |  |  |  |  |  | 
| 171 | 40 |  |  |  |  | 297 | push @queue, map "$file/$_", @newfiles; | 
| 172 |  |  |  |  |  |  | } | 
| 173 | 58 | 100 |  |  |  | 1002 | if ( -f $file ) { | 
| 174 | 18 | 100 |  |  |  | 107 | next unless $file =~ /\.pm$/; | 
| 175 |  |  |  |  |  |  |  | 
| 176 | 2 |  |  |  |  | 508 | my @parts = File::Spec->splitdir($file); | 
| 177 | 2 | 50 | 33 |  |  | 25 | shift @parts if @parts && exists $starters{ $parts[0] }; | 
| 178 | 2 | 50 | 33 |  |  | 23 | shift @parts if @parts && $parts[0] eq "lib"; | 
| 179 | 2 | 50 |  |  |  | 38 | $parts[-1] =~ s/\.pm$// if @parts; | 
| 180 |  |  |  |  |  |  |  | 
| 181 |  |  |  |  |  |  | # Untaint the parts | 
| 182 | 2 |  |  |  |  | 8 | for (@parts) { | 
| 183 | 8 | 50 | 33 |  |  | 562 | if ( /^([a-zA-Z0-9_\.\-]+)$/ && ( $_ eq $1 ) ) { | 
| 184 | 8 |  |  |  |  | 24 | $_ = $1;    # Untaint the original | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  | else { | 
| 187 | 0 |  |  |  |  | 0 | die qq{Invalid and untaintable filename "$file"!}; | 
| 188 |  |  |  |  |  |  | } | 
| 189 |  |  |  |  |  |  | } | 
| 190 | 2 |  |  |  |  | 10 | my $module = join( "::", @parts ); | 
| 191 | 2 |  |  |  |  | 14 | push( @modules, $module ); | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  | }    # while | 
| 194 |  |  |  |  |  |  |  | 
| 195 | 2 |  |  |  |  | 13 | return @modules; | 
| 196 |  |  |  |  |  |  | } | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | sub _starting_points { | 
| 199 | 1 | 50 |  | 1 |  | 33 | return 'blib' if -e 'blib'; | 
| 200 | 0 |  |  |  |  |  | return 'lib'; | 
| 201 |  |  |  |  |  |  | } | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | =head1 AUTHOR | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | Andrey Kostenko, C<<  >> | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | =head1 BUGS | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | Please report any bugs or feature requests to C, or through | 
| 210 |  |  |  |  |  |  | the web interface at L.  I will be notified, and then you'll | 
| 211 |  |  |  |  |  |  | automatically be notified of progress on your bug as I make changes. | 
| 212 |  |  |  |  |  |  |  | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | =head1 SUPPORT | 
| 217 |  |  |  |  |  |  |  | 
| 218 |  |  |  |  |  |  | You can find documentation for this module with the perldoc command. | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | perldoc Test::Pod::Coverage::Permissive | 
| 221 |  |  |  |  |  |  |  | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | You can also look for information at: | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | =over 4 | 
| 226 |  |  |  |  |  |  |  | 
| 227 |  |  |  |  |  |  | =item * RT: CPAN's request tracker | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | L | 
| 230 |  |  |  |  |  |  |  | 
| 231 |  |  |  |  |  |  | =item * AnnoCPAN: Annotated CPAN documentation | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | L | 
| 234 |  |  |  |  |  |  |  | 
| 235 |  |  |  |  |  |  | =item * CPAN Ratings | 
| 236 |  |  |  |  |  |  |  | 
| 237 |  |  |  |  |  |  | L | 
| 238 |  |  |  |  |  |  |  | 
| 239 |  |  |  |  |  |  | =item * Search CPAN | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | L | 
| 242 |  |  |  |  |  |  |  | 
| 243 |  |  |  |  |  |  | =back | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  |  | 
| 246 |  |  |  |  |  |  | =head1 ACKNOWLEDGEMENTS | 
| 247 |  |  |  |  |  |  |  | 
| 248 |  |  |  |  |  |  | Thanks to author of L. 90% of this module is a copy-paste from L. | 
| 249 |  |  |  |  |  |  |  | 
| 250 |  |  |  |  |  |  | =head1 LICENSE AND COPYRIGHT | 
| 251 |  |  |  |  |  |  |  | 
| 252 |  |  |  |  |  |  | Copyright 2010 Andrey Kostenko, based on Andy Lester's L | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it | 
| 255 |  |  |  |  |  |  | under the terms of either: the GNU General Public License as published | 
| 256 |  |  |  |  |  |  | by the Free Software Foundation; or the Artistic License. | 
| 257 |  |  |  |  |  |  |  | 
| 258 |  |  |  |  |  |  | See http://dev.perl.org/licenses/ for more information. | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | =cut | 
| 262 |  |  |  |  |  |  |  | 
| 263 |  |  |  |  |  |  | 1;    # End of Test::Pod::Coverage::Permissive |