File Coverage

blib/lib/Test/Pod/CoverageChange.pm
Criterion Covered Total %
statement 103 105 98.1
branch 27 32 84.3
condition 23 34 67.6
subroutine 20 21 95.2
pod 1 1 100.0
total 174 193 90.1


line stmt bran cond sub pod time code
1             package Test::Pod::CoverageChange;
2             # ABSTRACT: Test Perl files for POD coverage and syntax changes
3              
4 5     5   777197 use strict;
  5         12  
  5         195  
5 5     5   25 use warnings;
  5         15  
  5         547  
6              
7             our $VERSION = '0.005';
8             our $AUTHORITY = 'cpan:DERIV'; # AUTHORITY
9              
10 5     5   2799 use utf8;
  5         1610  
  5         30  
11              
12             =encoding utf8
13              
14             =head1 NAME
15              
16             Test::Pod::CoverageChange - Test Perl files for POD coverage and syntax changes
17              
18             =head1 SYNOPSIS
19              
20             use Test::Pod::CoverageChange qw(pod_coverage_syntax_ok);
21              
22             pod_coverage_syntax_ok('lib', {
23             MyModule::Bar => 3, ## expected to have 3 naked subs
24             MyModule::Foo => 10, ## expected to have 10 naked subs
25             MyModule::Baz => 1, ## expected to have 1 naked subs
26             MyModule::Qux => 5, ## expected to have 5 naked subs
27             }, [
28             We::Ignore::ThisModule,
29             We::Also::Ignore::This::Module
30             ],[
31             'a_sub_name_to_ignore'
32             qr/regexes are also acceptable/
33             ]);
34              
35             =head1 DESCRIPTION
36              
37             C is a helper combining L and
38             L to test for both POD coverage and syntax changes for a module
39             distribution at once, via a single call to L.
40              
41             Possible results
42              
43             =over 4
44              
45             =item * B if the file has no POD syntax or coverage error.
46              
47             =item * B if latest changes increased/decreased numbers of naked subs for the packages that have allowed naked subs.
48              
49             =item * B if a package allowed to have naked subs has 100% POD coverage.
50              
51             =item * B if a file in a given path has POD syntax error or has no POD.
52              
53             =back
54              
55             Ignores packages that passed as ignored package in the c<$ignored_package> argument into the pod_coverage_syntax_ok sub.
56              
57             =cut
58              
59 5     5   988 use Test::More;
  5         131869  
  5         42  
60 5     5   5025 use Pod::Checker;
  5         293845  
  5         927  
61 5     5   60 use Pod::Coverage;
  5         25  
  5         203  
62 5     5   3202 use File::Find::Rule;
  5         56822  
  5         52  
63 5     5   3663 use Test::Pod::Coverage;
  5         8677  
  5         42  
64 5     5   1132 use Module::Path qw(module_path);
  5         963  
  5         411  
65 5     5   40 use List::Util qw(any);
  5         12  
  5         357  
66 5     5   4858 use Path::Tiny;
  5         86782  
  5         543  
67             use constant {
68 5         719 POD_SYNTAX_IS_OK => 0,
69             FILE_HAS_NO_POD => -1,
70 5     5   63 };
  5         12  
71              
72 5     5   57 use Exporter qw(import export_to_level);
  5         25  
  5         8667  
73             our @EXPORT_OK = qw(pod_coverage_syntax_ok);
74              
75             =head2 pod_coverage_syntax_ok
76              
77             Checks all the modules under a given directory against POD coverage and POD syntax
78              
79             =over 4
80              
81             =item * C<$path> - path or arrayref of directories to check (recursively)
82              
83             example: ['lib', 'other directory'] | 'lib'
84              
85             =item * C<$allowed_naked_packages> - hashref of number of allowed naked subs, keyed by package name (optional)
86              
87             example: {Package1 => 2, Package2 => 1, Package3 => 10}
88              
89             =item * C<$ignored_packages> - arrayref of packages that will be ignored in the checks (optional)
90              
91             example: ['MyPackage1', 'MyPackage2', 'MyPackage3']
92              
93             =item * C<$ignored_subs> - arrayref of subnames or regexes that will be ignored in the checks (optional)
94              
95             example: ['a_sub_name', qr/a regex/]
96              
97             =back
98              
99             =cut
100              
101             sub pod_coverage_syntax_ok {
102 11     11 1 1127272 my %args = @_;
103 11         115 my %default_values = (path => 'lib', allowed_naked_packages => {}, ignored_packages => [], ignored_subs => []);
104              
105 11         66 %args = (%default_values, %args);
106              
107 11   50     60 my $path = $args{path} // 'lib';
108 11   50     46 my $ignored_packages = $args{ignored_packages} // [];
109              
110 11 100       56 $path = [$path] unless ref $path eq 'ARRAY';
111 11 50       42 $ignored_packages = [$ignored_packages] unless ref $ignored_packages eq 'ARRAY';
112              
113 11         55 _check_pod_coverage($path, $args{allowed_naked_packages}, $ignored_packages, $args{ignored_subs});
114 11         68 _check_pod_syntax($path, $ignored_packages);
115              
116 11         76 return undef;
117             }
118              
119             =head2 _check_pod_coverage
120              
121             Checks POD coverage for all the modules that exist under the given directory.
122             Passes the C<$allowed_naked_packages> to L.
123             Ignores the packages in the C<$ignored_packages> parameter.
124              
125             =over 4
126              
127             =item * C<$path> - path or arrayref of directories to check (recursively)
128              
129             =item * C<$allowed_naked_packages> - hashref of number of allowed naked subs, keyed by package name (optional)
130              
131             =item * C<$ignored_packages> - arrayref of packages that will be ignored in the checks (optional)
132              
133             =back
134              
135             =cut
136              
137             sub _check_pod_coverage {
138 11   50 11   64 my $path = shift // 'lib';
139 11   50     61 my $allowed_naked_packages = shift // {};
140 11   50     36 my $ignored_packages = shift // [];
141 11   50     43 my $ignored_subs = shift // [];
142              
143 11 100       51 _check_allowed_naked_packages($allowed_naked_packages, $ignored_packages) if keys %$allowed_naked_packages;
144              
145             # Check for newly added packages PODs
146 11         37 my @ignored_packages = (keys %$allowed_naked_packages, @$ignored_packages);
147 11         68 foreach my $package (Test::Pod::Coverage::all_modules(@$path)) {
148 15 100   15   2807 next if any { $_ eq $package } @ignored_packages;
  15         52  
149 6 50 66     67 if(!pod_coverage_ok($package, {trustme => [qw(DOES META)], private => [], also_private => $ignored_subs})
150             && _package_is_object_pad($package)){
151 0         0 diag("Package $package is an Object::Pad class, Do you miss PODs of auto-generated methods?"
152             . 'it will generate method "new" and getter and setter of member fields like:
153             has $XXXX :reader; #=> will create a method "XXXX"
154             has $YYYY :writer: #=> will create a method "set_YYYY"');
155             }
156              
157             }
158              
159 11         10477 return undef;
160             }
161              
162             sub _package_is_object_pad{
163 2     2   9473 my $package = shift;
164 2         12 my $file = module_path($package);
165 2         244 return path($file)->slurp_utf8 =~ /Object::Pad/;
166             }
167              
168             =head2 _check_pod_syntax
169              
170             Check POD syntax for all the modules that exist under the given directory.
171              
172             =over 4
173              
174             =item * C<$path> - path or arrayref of directories to check (recursively)
175              
176             =item * C<$ignored_packages> - arrayref of packages that will be ignored in the checks (optional)
177              
178             =back
179              
180             =cut
181              
182             sub _check_pod_syntax {
183 11   50 11   38 my $path = shift // 'lib';
184 11   50     31 my $ignored_packages = shift // [];
185              
186 11         76 my $Test_Builder = Test::More->builder;
187              
188 11         145 my @ignored_packages_full_path = ();
189 11         38 for (@$ignored_packages) {
190 1         4 my $file_path = module_path($_);
191 1 50       161 push @ignored_packages_full_path, $file_path if defined $file_path;
192             }
193              
194 11         444 my @files_path = File::Find::Rule->file()->name('*.p[m|l]')->in(@$path);
195              
196 11         12146 for my $file_path (@files_path) {
197 15 100   1   1899 next if any { /\Q$file_path/ } @ignored_packages_full_path;
  1         22  
198              
199 14         101 my $check_result = podchecker($file_path);
200 14 100       23959 if ($check_result == POD_SYNTAX_IS_OK) {
    100          
201 7         40 $Test_Builder->ok(1, sprintf("Pod structure is OK in the file %s.", $file_path));
202             } elsif ($check_result == FILE_HAS_NO_POD) {
203 6         37 $Test_Builder->todo_skip(sprintf("There is no POD in the file %s.", $file_path));
204             } else {
205 1         11 $Test_Builder->ok(0, sprintf("There are %d errors in the POD structure in the %s.", $check_result, $file_path));
206             }
207             }
208              
209 11         6525 return undef;
210             }
211              
212             =head2 _check_allowed_naked_packages
213              
214             Checks passed allowed_naked_packages against existing package files.
215              
216             =over 4
217              
218             =item * C<$allowed_naked_packages> - hashref of number of allowed naked subs, keyed by package name (optional)
219              
220             =item * C<$ignored_packages> - a list of packages that will be ignored in our checks, supports arrayref (optional)
221              
222             =back
223              
224             Possible results
225              
226             =over 4
227              
228             =item * B if the numbers of existing naked subs are equal to passed value.
229              
230             =item * B if the number of existing naked subs are not equal to the passed value.
231              
232             =item * B if a package has 100% POD coverage and it passed as a L<$allowed_naked_package>.
233              
234             =back
235              
236             =cut
237              
238             sub _check_allowed_naked_packages {
239 6   50 6   18 my $allowed_naked_packages = shift // {};
240 6   50     28 my $ignored_packages = shift // [];
241              
242 6         50 my $Test_Builder = Test::More->builder;
243 6         95 my $caller_test_file = (caller(2))[1];
244              
245             # Check for the currently naked packages POD.
246 6         31 foreach my $package (sort keys %$allowed_naked_packages) {
247 8 50   0   82 next if any { /^\Q$package\E$/ } @$ignored_packages;
  0         0  
248              
249 8         243 my $pc = Pod::Coverage->new(
250             package => $package,
251             private => []);
252              
253             # Require before we run the coverage to pick up any errors loading that
254             # are getting hidden by the coverage check.
255             # Do not move to after $pc usage otherwise $@ will reflect the failure
256             # from coverage which won't be the same.
257 8         793 eval qq{ require $package };
258 8 50       39 die if ($@);
259              
260 8   100     6825 my $fully_covered = defined $pc->coverage && $pc->coverage == 1;
261 8 100       6959 my $coverage_percentage = defined $pc->coverage ? $pc->coverage * 100 : 0;
262 8         2193 my $max_expected_naked_subs = $allowed_naked_packages->{$package};
263 8   100     3533 my $naked_subs_count = scalar $pc->naked // scalar $pc->_get_syms($package);
264              
265 8 100       859 if (!$fully_covered) {
266 7         90 $Test_Builder->todo_skip(sprintf("You have %.2f%% POD coverage for the module '%s'.", $coverage_percentage, $package));
267             }
268              
269 8 100 100     4524 if (!$fully_covered && $naked_subs_count < $max_expected_naked_subs) {
    100 100        
270 1         9 $Test_Builder->ok(
271             0,
272             sprintf(
273             "Your last changes decreased the number of naked subs in the %s package.
274             Change the %s => %s in the %s file please.", $package, $package, $naked_subs_count, $caller_test_file
275             ));
276 1         1303 next;
277             } elsif (!$fully_covered && $naked_subs_count > $max_expected_naked_subs) {
278 2         18 $Test_Builder->ok(0, sprintf("Your last changes increased the number of naked subs in the %s package from %s to %s. Please add pod for your new subs.",
279             $package, $max_expected_naked_subs, $naked_subs_count));
280 2         1953 next;
281             }
282              
283 5 100       33 if ($fully_covered) {
284 1         8 $Test_Builder->ok(
285             0,
286             sprintf(
287             '%s modules has 100%% POD coverage. Please remove it from the %s file $allowed_naked_packages variable to fix this error.',
288             $package, $caller_test_file
289             ));
290             }
291             }
292              
293 6         1730 return undef;
294             }
295              
296             1;