File Coverage

blib/lib/Test/Dependencies.pm
Criterion Covered Total %
statement 134 142 94.3
branch 33 46 71.7
condition 31 47 65.9
subroutine 13 13 100.0
pod 1 1 100.0
total 212 249 85.1


line stmt bran cond sub pod time code
1             package Test::Dependencies;
2              
3 5     5   184767 use warnings;
  5         32  
  5         169  
4 5     5   24 use strict;
  5         10  
  5         94  
5              
6 5     5   19 use Carp;
  5         10  
  5         313  
7 5     5   16643 use Module::CoreList;
  5         660105  
  5         282  
8 5     5   6747 use Pod::Strip;
  5         148951  
  5         202  
9              
10 5     5   1605 use parent 'Test::Builder::Module';
  5         824  
  5         35  
11              
12             =head1 NAME
13              
14             Test::Dependencies - Ensure that the dependency listing is complete
15              
16             =head1 VERSION
17              
18             Version 0.30
19              
20             =cut
21              
22             our $VERSION = '0.30';
23              
24             =head1 SYNOPSIS
25              
26             In your t/00-dependencies.t:
27              
28             use CPAN::Meta; # or CPAN::Meta::cpanfile
29             use File::Find::Rule::Perl;
30              
31             use Test::More;
32             use Test::Dependencies '0.28' forward_compatible => 1;
33              
34             my $meta = CPAN::Meta->load_file('META.json'); # or META.yml
35             plan skip_all => 'No META.json' if ! $meta;
36              
37             my @files = File::Find::Rule::Perl->perl_files->in('./lib', './bin');
38             ok_dependencies($meta, \@files, [qw/runtime configure build test/],
39             undef, # all features in the cpanfile
40             ignores => [ qw/ Your::Namespace Some::Other::Namespace / ]
41             );
42              
43             done_testing;
44              
45             =head1 DESCRIPTION
46              
47             Makes sure that all of the modules that are 'use'd are listed in the
48             Makefile.PL as dependencies.
49              
50             =head1 OPTIONS
51              
52             B You can pass options to the module via the 'use' line.
53             These options will be moved to the ok_dependencies() function.
54             The available options are:
55              
56             =over 4
57              
58             =item forward_compatible
59              
60             When specified and true, stops the module from outputting a plan, which
61             is the default mode of operation when the module becomes 1.0.
62              
63             =item exclude
64              
65             Specifies the list of namespaces for which it is ok not to have
66             specified dependencies.
67              
68             =item style
69              
70             B
71              
72             There used to be the option of specifying a style; the heavy style
73             depended on B::PerlReq. This module stopped working somewhere around
74             Perl 5.20. Specifying a style no longer has any effect.
75              
76             =back
77              
78             =cut
79              
80             our @EXPORT = qw/ok_dependencies/;
81              
82             our $exclude_re;
83              
84             sub import {
85 4     4   59 my $package = shift;
86 4         18 my %args = @_;
87 4         15 my $callerpack = caller;
88 4         49 my $tb = __PACKAGE__->builder;
89 4         79 $tb->exported_to($callerpack);
90              
91 4 50       53 unless ($args{forward_compatible}) {
92 4         23 $tb->no_plan;
93              
94 4 100       831 if (defined $args{exclude}) {
95 3         7 foreach my $namespace (@{$args{exclude}}) {
  3         10  
96 10 50       64 croak "$namespace is not a valid namespace"
97             unless $namespace =~ m/^(?:(?:\w+::)|)+\w+$/;
98             }
99 3         8 $exclude_re = join '|', map { "^$_(\$|::)" } @{$args{exclude}};
  10         36  
  3         10  
100             }
101             else {
102 1         8 $exclude_re = qr/^$/;
103             }
104             }
105              
106 4         3414 $package->export_to_level(1, '', qw/ok_dependencies/);
107             }
108              
109              
110             sub _get_modules_used_in_file {
111 12     12   23 my $file = shift;
112 12         33 my ($fh, $code);
113 12         0 my %used;
114              
115 12         39 local $/;
116 12 50       534 open $fh, $file or return undef;
117 12         343 my $data = <$fh>;
118 12         121 close $fh;
119 12         108 my $p = Pod::Strip->new;
120 12         608 $p->output_string(\$code);
121 12         5193 $p->parse_string_document($data);
122 12         68741 $used{$2}++ while $code =~ /^\s*(use|with|extends)\s+['"]?([\w:.]+)['"]?/gm;
123 12         172 while ($code =~ m{^\s*use\s+base
124             \s+(?:qw.|(?:(?:['"]|q.|qq.)))([\w\s:]+)}gmx) {
125 0         0 $used{$_}++ for split ' ', $1;
126             }
127              
128 12         237 return [keys %used];
129             }
130              
131             sub _get_modules_used {
132 3     3   10 my ($files) = @_;
133 3         9 my @modules;
134              
135 3         18 foreach my $file (sort @$files) {
136 12         34 my $ret = _get_modules_used_in_file($file);
137 12 50       37 if (! defined $ret) {
138 0         0 die "Could not determine modules used in '$file'";
139             }
140 12         47 push @modules, @$ret;
141             }
142 3         18 return @modules;
143             }
144              
145             sub _legacy_ok_dependencies {
146 4     4   11 my ($missing_dep);
147 4         39 my $tb = __PACKAGE__->builder;
148             {
149 4         69 local $@;
  4         8  
150              
151 0         0 eval {
152 5     5   123719 use CPAN::Meta;
  5         82181  
  5         208  
153 4         11 };
154 0         0 eval {
155 5     5   1663 use File::Find::Rule::Perl;
  5         41853  
  5         71  
156 4         9 };
157              
158 4         9 $missing_dep = $@;
159             }
160 4 50       15 die $missing_dep if $missing_dep;
161              
162 4         12 my $meta;
163 4         10 for my $file (qw/ META.json META.yml /) {
164 6 100       127 if (-r $file) {
165 3         27 $tb->ok(1, "$file is present and readable");
166 3         1193 $meta = CPAN::Meta->load_file($file);
167 3         115395 last;
168             }
169             }
170              
171 4 100       40 if (! $meta) {
172 1         6 $tb->level(2);
173 1         13 $tb->ok(0, "Missing META.{yml,json} file for dependency checking");
174 1         1010 $tb->diag("Use the non-legacy invocation to provide the info");
175 1         194 return;
176             }
177              
178             my @run = File::Find::Rule::Perl->perl_file->in(
179 3         54 grep { -e $_ } ('./bin', './lib', './t'));
  9         2669  
180              
181 3         6216 ok_dependencies($meta, \@run, ignores => [ 'ExtUtils::MakeMaker']);
182             }
183              
184              
185             =head1 EXPORTED FUNCTIONS
186              
187             =head2 ok_dependencies($meta, $files, $phases, $features, %options)
188              
189             $meta is a CPAN::Meta object
190             $files is an arrayref with files to be scanned
191              
192             =head3 %options keys
193              
194             =over
195              
196             =item phases
197              
198             This is an arrayref holding one or more names of phases
199             as defined by L, or undef for all
200              
201             =item features
202              
203             This is an arrayref holding zero or more names of features, or undef for all
204              
205             =item ignores
206              
207             This is a arrayref listing the names of modules (and their sub-namespaces)
208             for which no errors are to be reported.
209              
210             =back
211              
212             =head2 ok_dependencies()
213              
214             B Legacy invocation to be removed. In previous versions,
215             this function would scan the I bin/, lib/ and t/ subtrees, with the
216             exception of a few sub-directories known to be used by version control
217             systems.
218              
219             This behaviour has been changed: as of 0.20, Find::File::Rule::Perl
220             is being used to find Perl files (*.pl, *.pm, *.t and those starting with
221             a shebang line referring to perl).
222              
223             =cut
224              
225             sub ok_dependencies {
226              
227 7 100   7 1 3752 return _legacy_ok_dependencies
228             unless @_;
229              
230 3         16 my ($meta, $files, %options) = @_;
231 3         10 my $phases = $options{phases};
232 3         8 my $features = $options{features};
233             my $ignores_re = '^(?:' . join('|',
234             # create regex for sub-namespaces
235 3         21 map { "$_(?:::.*)?" }
236 3   50     9 @{$options{ignores} // []})
  3         20  
237             . ')$';
238 3         54 $ignores_re = qr/$ignores_re/;
239              
240 3   66     30 $features //= $meta->features;
241 3 50       966 $features = [ $features ] unless ref $features;
242 3   50     26 $phases //= [ 'runtime', 'configure', 'build', 'test', 'develop' ];
243 3 50       12 $phases = [ $phases ] unless ref $phases;
244              
245 3         31 my $tb = __PACKAGE__->builder;
246 3         60 my %used = map { $_ => 1 } _get_modules_used($files);
  34         64  
247              
248 3         24 my @meta_features = map { $_->identifier } $meta->features;
  2         1135  
249 3         49 my $prereqs = $meta->effective_prereqs(\@meta_features);
250 3         11802 my $reqs = [];
251              
252             push @$reqs, $prereqs->requirements_for($_, 'requires')
253 3         20 for (@$phases);
254              
255 3         539 my $min_perl_ver;
256             my $minimum_perl;
257 3         11 for (map { $_->requirements_for_module('perl') } @$reqs) {
  15         182  
258 2 50       21 next if ! defined $_;
259              
260 2         29 my $ver = version->parse($_)->numify;
261 2 50 33     16 $minimum_perl = (defined $min_perl_ver and $min_perl_ver < $ver)
262             ? $minimum_perl : $_;
263 2 50 33     43 $min_perl_ver = (defined $min_perl_ver and $min_perl_ver < $ver)
264             ? $min_perl_ver : $ver;
265             }
266 3   100     22 $minimum_perl //= "v5.0.0";
267 3   100     14 $min_perl_ver //= 5.0;
268              
269 3         8 for my $req (@$reqs) {
270 15         66 for my $mod (sort $req->required_modules) {
271 28 100 100     3227 next if ($mod eq 'perl'
      66        
      100        
272             or $mod =~ $ignores_re
273             or ($exclude_re and $mod =~ $exclude_re));
274              
275             # if the module is/was deprecated from CORE,
276             # it makes sense to require it, if the dependency exists
277 16 50 33     65 next if (Module::CoreList->deprecated_in($mod)
278             or Module::CoreList->removed_from($mod));
279              
280 16         38758 my $req_version = $req->requirements_for_module($mod);
281 16         832 my $first_in = Module::CoreList->first_release($mod, $req_version);
282 16 100       12311 my $verstr = ($req_version) ? '(' . $req_version . ')' : '';
283 16         192 my $corestr = version->parse($first_in)->normal;
284 16 100       145 $tb->ok($first_in > $min_perl_ver,
285             "Required module '$mod'$verstr "
286             . "in core (since $corestr) after minimum perl "
287             . $minimum_perl )
288             if defined $first_in;
289             }
290             }
291              
292 3         12 my %required;
293 3         12 for my $req (@$reqs) {
294             $required{$_} = $req->requirements_for_module($_)
295 15         927 for $req->required_modules;
296             }
297 3         261 delete $required{perl};
298              
299 3         21 foreach my $mod (sort keys %required) {
300 18 100 66     3156 $tb->ok(exists $used{$mod}, "Declared dependency $mod used")
      100        
301             unless ($mod =~ $ignores_re
302             or ($exclude_re and $mod =~ $exclude_re));
303             }
304              
305 3         587 foreach my $mod (sort keys %used) {
306 24 100 33     6449 next if ($mod =~ $ignores_re
      66        
      66        
307             or $mod =~ m/^v?\d+[.]\d+/ # minimum perl version requirement
308             or ($exclude_re and $mod =~ $exclude_re));
309              
310 22         103 my $first_in = Module::CoreList->first_release($mod, $required{$mod});
311 22         27922 my $v;
312 22 50       79 if ($v = Module::CoreList->removed_from($mod)) {
    50          
    100          
313 0         0 $v = version->parse($v)->normal;
314 0         0 $tb->ok(exists $required{$mod},
315             "Removed-from-CORE (in $v) module '$mod' "
316             . 'explicitly required');
317             }
318             elsif ($v = Module::CoreList->deprecated_in($mod)) {
319 0         0 $v = version->parse($v)->normal;
320 0         0 $tb->ok(exists $required{$mod},
321             "Deprecated-from-CORE (in $v) module '$mod' explicitly "
322             . 'required to anticipate removal');
323             }
324             elsif (defined $first_in) {
325 16         64905 $v = version->parse($first_in)->normal;
326 16   66     180 $tb->ok($first_in <= $min_perl_ver or exists $required{$mod},
327             "Used CORE module '$mod' in core before "
328             . "Perl $minimum_perl (since $v) "
329             . "or explicitly required");
330             }
331             else {
332 6         4578 $tb->ok(exists $required{$mod},
333             "Used non-CORE module '$mod' in requirements listing");
334             }
335             }
336             }
337              
338             =head1 AUTHORS
339              
340             =over 4
341              
342             =item * Jesse Vincent C<< >>
343              
344             =item * Alex Vandiver C<< >>
345              
346             =item * Zev Benjamin C<< >>
347              
348             =item * Erik Huelsmann C<< >>
349              
350             =back
351              
352             =head1 BUGS
353              
354             =over 4
355              
356             =item * Test::Dependencies does not track module version requirements.
357              
358             =back
359              
360             Please report your bugs on GitHub:
361              
362             L
363              
364             =head1 SUPPORT
365              
366             You can find documentation for this module with the perldoc command.
367              
368             perldoc Test::Dependencies
369              
370             You can also look for information at:
371              
372             =over 4
373              
374             =item * CPAN Ratings
375              
376             L
377              
378             =item * Search CPAN
379              
380             L
381              
382             =back
383              
384             =head1 LICENCE AND COPYRIGHT
385              
386             Copyright (c) 2016-2020, Erik Huelsmann. All rights reserved.
387             Copyright (c) 2007, Best Practical Solutions, LLC. All rights reserved.
388              
389             This module is free software; you can redistribute it and/or modify it
390             under the same terms as Perl itself. See perlartistic.
391              
392             DISCLAIMER OF WARRANTY
393              
394             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
395             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
396             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
397             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
398             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
399             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
400             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
401             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
402             NECESSARY SERVICING, REPAIR, OR CORRECTION.
403              
404             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
405             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
406             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE
407             TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR
408             CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
409             SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
410             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
411             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
412             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
413             DAMAGES.
414              
415             =cut
416              
417             1;