File Coverage

blib/lib/Test/Dependencies.pm
Criterion Covered Total %
statement 135 141 95.7
branch 34 48 70.8
condition 31 47 65.9
subroutine 13 13 100.0
pod 1 1 100.0
total 214 250 85.6


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