File Coverage

blib/lib/Test/Pod/CoverageChange.pm
Criterion Covered Total %
statement 101 103 98.0
branch 26 30 86.6
condition 23 34 67.6
subroutine 20 21 95.2
pod 1 1 100.0
total 171 189 90.4


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 4     4   259748 use strict;
  4         24  
  4         94  
5 4     4   17 use warnings;
  4         7  
  4         163  
6              
7             our $VERSION = '0.003';
8             our $AUTHORITY = 'cpan:DERIV'; # AUTHORITY
9              
10 4     4   2094 use utf8;
  4         49  
  4         18  
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 4     4   137 use Test::More;
  4         7  
  4         22  
60 4     4   2697 use Pod::Checker;
  4         126587  
  4         456  
61 4     4   33 use Pod::Coverage;
  4         9  
  4         152  
62 4     4   1938 use File::Find::Rule;
  4         28360  
  4         33  
63 4     4   2427 use Test::Pod::Coverage;
  4         4501  
  4         22  
64 4     4   251 use Module::Path qw(module_path);
  4         8  
  4         185  
65 4     4   20 use List::Util qw(any);
  4         6  
  4         375  
66 4     4   2805 use Path::Tiny;
  4         40432  
  4         246  
67             use constant {
68 4         315 POD_SYNTAX_IS_OK => 0,
69             FILE_HAS_NO_POD => -1,
70 4     4   34 };
  4         7  
71              
72 4     4   23 use Exporter qw(import export_to_level);
  4         18  
  4         3572  
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 36042 my %args = @_;
103 11         56 my %default_values = (path => 'lib', allowed_naked_packages => {}, ignored_packages => [], ignored_subs => []);
104              
105 11         49 %args = (%default_values, %args);
106              
107 11   50     47 my $path = $args{path} // 'lib';
108 11   50     31 my $ignored_packages = $args{ignored_packages} // [];
109              
110 11 100       55 $path = [$path] unless ref $path eq 'ARRAY';
111 11 50       36 $ignored_packages = [$ignored_packages] unless ref $ignored_packages eq 'ARRAY';
112              
113 11         41 _check_pod_coverage($path, $args{allowed_naked_packages}, $ignored_packages, $args{ignored_subs});
114 11         42 _check_pod_syntax($path, $ignored_packages);
115              
116 11         55 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   30 my $path = shift // 'lib';
139 11   50     25 my $allowed_naked_packages = shift // {};
140 11   50     40 my $ignored_packages = shift // [];
141 11   50     39 my $ignored_subs = shift // [];
142              
143 11 100       47 _check_allowed_naked_packages($allowed_naked_packages, $ignored_packages) if keys %$allowed_naked_packages;
144              
145             # Check for newly added packages PODs
146 11         43 my @ignored_packages = (keys %$allowed_naked_packages, @$ignored_packages);
147 11         60 foreach my $package (Test::Pod::Coverage::all_modules(@$path)) {
148 15 100   15   2292 next if any { $_ eq $package } @ignored_packages;
  15         51  
149 6 50 66     55 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         7176 return undef;
160             }
161              
162             sub _package_is_object_pad{
163 2     2   5707 my $package = shift;
164 2         11 my $file = module_path($package);
165 2         207 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     29 my $ignored_packages = shift // [];
185              
186 11         65 my $Test_Builder = Test::More->builder;
187              
188 11         119 my @ignored_packages_full_path = ();
189 11         34 for (@$ignored_packages) {
190 1         5 my $file_path = module_path($_);
191 1 50       98 push @ignored_packages_full_path, $file_path if defined $file_path;
192             }
193              
194 11         307 my @files_path = File::Find::Rule->file()->name('*.p[m|l]')->in(@$path);
195              
196 11         9707 for my $file_path (@files_path) {
197 15 100   1   1325 next if any { /\Q$file_path/ } @ignored_packages_full_path;
  1         16  
198              
199 14         104 my $check_result = podchecker($file_path);
200 14 100       19350 if ($check_result == POD_SYNTAX_IS_OK) {
    100          
201 7         42 $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         43 $Test_Builder->todo_skip(sprintf("There is no POD in the file %s.", $file_path));
204             } else {
205 1         15 $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         4241 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   16 my $allowed_naked_packages = shift // {};
240 6   50     16 my $ignored_packages = shift // [];
241              
242 6         27 my $Test_Builder = Test::More->builder;
243 6         77 my $caller_test_file = (caller(2))[1];
244              
245             # Check for the currently naked packages POD.
246 6         49 foreach my $package (sort keys %$allowed_naked_packages) {
247 8 50   0   62 next if any { /^\Q$package\E$/ } @$ignored_packages;
  0         0  
248              
249 8         138 my $pc = Pod::Coverage->new(
250             package => $package,
251             private => []);
252 8   100     6555 my $fully_covered = defined $pc->coverage && $pc->coverage == 1;
253 8 100       6662 my $coverage_percentage = defined $pc->coverage ? $pc->coverage * 100 : 0;
254 8         1611 my $max_expected_naked_subs = $allowed_naked_packages->{$package};
255 8   100     4262 my $naked_subs_count = scalar $pc->naked // scalar $pc->_get_syms($package);
256              
257 8 100       867 if (!$fully_covered) {
258 7         116 $Test_Builder->todo_skip(sprintf("You have %.2f%% POD coverage for the module '%s'.", $coverage_percentage, $package));
259             }
260              
261 8 100 100     2932 if (!$fully_covered && $naked_subs_count < $max_expected_naked_subs) {
    100 100        
262 1         10 $Test_Builder->ok(
263             0,
264             sprintf(
265             "Your last changes decreased the number of naked subs in the %s package.
266             Change the %s => %s in the %s file please.", $package, $package, $naked_subs_count, $caller_test_file
267             ));
268 1         1071 next;
269             } elsif (!$fully_covered && $naked_subs_count > $max_expected_naked_subs) {
270 2         23 $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.",
271             $package, $max_expected_naked_subs, $naked_subs_count));
272 2         2174 next;
273             }
274              
275 5 100       24 if ($fully_covered) {
276 1         7 $Test_Builder->ok(
277             0,
278             sprintf(
279             '%s modules has 100%% POD coverage. Please remove it from the %s file $allowed_naked_packages variable to fix this error.',
280             $package, $caller_test_file
281             ));
282             }
283             }
284              
285 6         1091 return undef;
286             }
287              
288             1;