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   257052 use strict;
  4         25  
  4         95  
5 4     4   18 use warnings;
  4         8  
  4         160  
6              
7             our $VERSION = '0.002';
8             our $AUTHORITY = 'cpan:DERIV'; # AUTHORITY
9              
10 4     4   1912 use utf8;
  4         49  
  4         28  
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   153 use Test::More;
  4         8  
  4         20  
60 4     4   2538 use Pod::Checker;
  4         128414  
  4         407  
61 4     4   30 use Pod::Coverage;
  4         8  
  4         148  
62 4     4   1806 use File::Find::Rule;
  4         27987  
  4         26  
63 4     4   1919 use Test::Pod::Coverage;
  4         4474  
  4         19  
64 4     4   242 use Module::Path qw(module_path);
  4         6  
  4         180  
65 4     4   22 use List::Util qw(any);
  4         8  
  4         364  
66 4     4   2832 use Path::Tiny;
  4         40075  
  4         225  
67             use constant {
68 4         283 POD_SYNTAX_IS_OK => 0,
69             FILE_HAS_NO_POD => -1,
70 4     4   30 };
  4         6  
71              
72 4     4   23 use Exporter qw(import export_to_level);
  4         18  
  4         3611  
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 34327 my %args = @_;
103 11         47 my %default_values = (path => 'lib', allowed_naked_packages => {}, ignored_packages => [], ignored_subs => []);
104              
105 11         44 %args = (%default_values, %args);
106              
107 11   50     37 my $path = $args{path} // 'lib';
108 11   50     28 my $ignored_packages = $args{ignored_packages} // [];
109              
110 11 100       50 $path = [$path] unless ref $path eq 'ARRAY';
111 11 50       32 $ignored_packages = [$ignored_packages] unless ref $ignored_packages eq 'ARRAY';
112              
113 11         32 _check_pod_coverage($path, $args{allowed_naked_packages}, $ignored_packages, $args{ignored_subs});
114 11         33 _check_pod_syntax($path, $ignored_packages);
115              
116 11         37 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   23 my $path = shift // 'lib';
139 11   50     20 my $allowed_naked_packages = shift // {};
140 11   50     38 my $ignored_packages = shift // [];
141 11   50     30 my $ignored_subs = shift // [];
142              
143 11 100       41 _check_allowed_naked_packages($allowed_naked_packages, $ignored_packages) if keys %$allowed_naked_packages;
144              
145             # Check for newly added packages PODs
146 11         29 my @ignored_packages = (keys %$allowed_naked_packages, @$ignored_packages);
147 11         36 foreach my $package (Test::Pod::Coverage::all_modules(@$path)) {
148 15 100   15   2216 next if any { $_ eq $package } @ignored_packages;
  15         46  
149 6 50 66     52 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         6589 return undef;
160             }
161              
162             sub _package_is_object_pad{
163 2     2   5223 my $package = shift;
164 2         5 my $file = module_path($package);
165 2         166 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   28 my $path = shift // 'lib';
184 11   50     25 my $ignored_packages = shift // [];
185              
186 11         44 my $Test_Builder = Test::More->builder;
187              
188 11         102 my @ignored_packages_full_path = ();
189 11         25 for (@$ignored_packages) {
190 1         3 my $file_path = module_path($_);
191 1 50       83 push @ignored_packages_full_path, $file_path if defined $file_path;
192             }
193              
194 11         262 my @files_path = File::Find::Rule->file()->name('*.p[m|l]')->in(@$path);
195              
196 11         8343 for my $file_path (@files_path) {
197 15 100   1   1315 next if any { /\Q$file_path/ } @ignored_packages_full_path;
  1         14  
198              
199 14         60 my $check_result = podchecker($file_path);
200 14 100       18593 if ($check_result == POD_SYNTAX_IS_OK) {
    100          
201 7         43 $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         34 $Test_Builder->todo_skip(sprintf("There is no POD in the file %s.", $file_path));
204             } else {
205 1         7 $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         3707 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   14 my $allowed_naked_packages = shift // {};
240 6   50     13 my $ignored_packages = shift // [];
241              
242 6         30 my $Test_Builder = Test::More->builder;
243 6         74 my $caller_test_file = (caller(2))[1];
244              
245             # Check for the currently naked packages POD.
246 6         26 foreach my $package (sort keys %$allowed_naked_packages) {
247 8 50   0   52 next if any { /^\Q$package\E$/ } @$ignored_packages;
  0         0  
248              
249 8         106 my $pc = Pod::Coverage->new(
250             package => $package,
251             private => []);
252 8   100     6050 my $fully_covered = defined $pc->coverage && $pc->coverage == 1;
253 8 100       6561 my $coverage_percentage = defined $pc->coverage ? $pc->coverage * 100 : 0;
254 8         1643 my $max_expected_naked_subs = $allowed_naked_packages->{$package};
255 8   100     4121 my $naked_subs_count = scalar $pc->naked // scalar $pc->_get_syms($package);
256              
257 8 100       630 if (!$fully_covered) {
258 7         396 $Test_Builder->todo_skip(sprintf("You have %.2f%% POD coverage for the module '%s'.", $coverage_percentage, $package));
259             }
260              
261 8 100 100     2487 if (!$fully_covered && $naked_subs_count < $max_expected_naked_subs) {
    100 100        
262 1         8 $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         988 next;
269             } elsif (!$fully_covered && $naked_subs_count > $max_expected_naked_subs) {
270 2         16 $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         2056 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         1090 return undef;
286             }
287              
288             1;