File Coverage

blib/lib/Dist/Zilla/Plugin/Test/ReportPrereqs.pm
Criterion Covered Total %
statement 60 61 98.3
branch 4 8 50.0
condition 1 3 33.3
subroutine 16 16 100.0
pod 0 3 0.0
total 81 91 89.0


line stmt bran cond sub pod time code
1 1     1   1478264 use 5.020;
  1         5  
2 1     1   11 use warnings;
  1         2  
  1         148  
3              
4             package Dist::Zilla::Plugin::Test::ReportPrereqs;
5             # ABSTRACT: Report on prerequisite versions during automated testing
6              
7             our $VERSION = '0.029';
8              
9 1     1   9 use Dist::Zilla 4 ();
  1         21  
  1         34  
10              
11 1     1   12 use Moose;
  1         2  
  1         11  
12             with 'Dist::Zilla::Role::FileGatherer', 'Dist::Zilla::Role::PrereqSource';
13              
14 1     1   7027 use Sub::Exporter::ForMethods;
  1         2  
  1         14  
15             use Data::Section 0.200002 # encoding and bytes
16 1     1   232 { installer => Sub::Exporter::ForMethods::method_installer }, '-setup';
  1         24  
  1         7  
17              
18 1     1   1646 use Data::Dumper;
  1         6695  
  1         79  
19 1     1   10 use Moose::Util::TypeConstraints 'enum';
  1         2  
  1         13  
20              
21             sub mvp_multivalue_args {
22 1     1 0 215 return qw( include exclude );
23             }
24              
25             foreach my $attr (qw( include exclude )) {
26             has "${attr}s" => (
27             init_arg => $attr,
28             is => 'ro',
29             traits => ['Array'],
30             default => sub { [] },
31             handles => { "${attr}d_modules" => 'elements', },
32             );
33             }
34              
35             has verify_prereqs => (
36             is => 'ro',
37             isa => 'Bool',
38             default => 1,
39             );
40              
41             has version_extractor => (
42             is => 'ro',
43             isa => enum( [qw(ExtUtils::MakeMaker Module::Metadata)] ),
44             default => 'ExtUtils::MakeMaker',
45             );
46              
47             sub register_prereqs {
48 1     1 0 571654 my $self = shift;
49              
50 1         36 $self->zilla->register_prereqs(
51             {
52             phase => 'test',
53             type => 'requires',
54             },
55             'Test::More' => 0,
56             $self->version_extractor => 0,
57             'File::Spec' => 0,
58             );
59              
60 1         292 $self->zilla->register_prereqs(
61             {
62             phase => 'test',
63             type => 'recommends',
64             },
65             'CPAN::Meta' => '2.120900',
66             );
67             }
68              
69             sub gather_files {
70 1     1 0 78671 my $self = shift;
71              
72 1         8 my $data = $self->merged_section_data;
73 1 50 33     9481 return unless $data and %$data;
74              
75 1         8 require Dist::Zilla::File::InMemory;
76              
77 1         5 for my $filename ( keys %$data ) {
78             $self->add_file(
79             Dist::Zilla::File::InMemory->new(
80             {
81             name => $filename,
82 1         4 content => $self->_munge_test( ${ $data->{$filename} } ),
  1         7  
83             }
84             )
85             );
86             }
87              
88 1         1225 require Dist::Zilla::File::FromCode;
89             $self->add_file(
90             Dist::Zilla::File::FromCode->new(
91             {
92             name => $self->_dump_filename,
93 2     2   267925 code => sub { $self->_dump_prereqs },
94             }
95             )
96 1         82498 );
97              
98 1         760 return;
99             }
100              
101             sub _munge_test {
102 1     1   18 my ( $self, $guts ) = @_;
103 1 50       41 $guts =~ s{INSERT_VERSION_HERE}{$self->VERSION || '<self>'}e;
  1         28  
104 1         41 $guts =~ s{INSERT_DD_FILENAME_HERE}{'./' . $self->_dump_filename}e;
  1         6  
105 1         39 $guts =~ s{INSERT_INCLUDED_MODULES_HERE}{_format_list($self->included_modules)}e;
  1         55  
106 1         39 $guts =~ s{INSERT_EXCLUDED_MODULES_HERE}{_format_list($self->excluded_modules)}e;
  1         49  
107 1 50       40 $guts =~ s{INSERT_VERIFY_PREREQS_CONFIG}{$self->verify_prereqs ? 1 : 0}e;
  1         43  
108 1         40 $guts =~ s{VERSION_EXTRACTOR_MODULE}{$self->version_extractor}e;
  1         43  
109 1 50       43 if ( $self->version_extractor eq 'ExtUtils::MakeMaker' ) {
110 1         49 $guts
111             =~ s'VERSION_EXTRACTION'MM->parse_version( File::Spec->catfile($prefix, $file) )';
112              
113             }
114             else { # Module::Metadata
115 0         0 $guts
116             =~ s'VERSION_EXTRACTION'Module::Metadata->new_from_file( File::Spec->catfile($prefix, $file) )->version';
117             }
118              
119 1         38 return $guts;
120             }
121              
122 2     2   61 sub _dump_filename { 't/00-report-prereqs.dd' }
123              
124             sub _format_list {
125 2     2   8 return join( "\n", map { " $_" } @_ );
  3         42  
126             }
127              
128             sub _dump_prereqs {
129 2     2   6 my $self = shift;
130 2         66 my $prereqs = $self->zilla->prereqs->as_string_hash;
131 2         1631 return ("do { my "
132             . Data::Dumper->new( [$prereqs], ['x'] )->Purity(1)->Sortkeys(1)->Terse(0)->Dump()
133             . ' $x;'
134             . "\n }" );
135             }
136              
137             __PACKAGE__->meta->make_immutable;
138              
139             1;
140              
141             #pod =for Pod::Coverage
142             #pod gather_files
143             #pod mvp_multivalue_args
144             #pod register_prereqs
145             #pod
146             #pod =head1 SYNOPSIS
147             #pod
148             #pod # in dist.ini
149             #pod [Test::ReportPrereqs]
150             #pod include = Acme::FYI
151             #pod exclude = Acme::Dont::Care
152             #pod
153             #pod =head1 DESCRIPTION
154             #pod
155             #pod This L<Dist::Zilla> plugin adds a F<t/00-report-prereqs.t> test file and an accompanying
156             #pod F<t/00-report-prereqs.dd> data file. It reports
157             #pod the version of all modules listed in the distribution metadata prerequisites
158             #pod (including 'recommends', 'suggests', etc.). However, any 'develop' prereqs
159             #pod are not reported (unless they show up in another category).
160             #pod
161             #pod If a F<MYMETA.json> file exists and L<CPAN::Meta> is installed on the testing
162             #pod machine, F<MYMETA.json> will be examined for prerequisites in addition, as it
163             #pod would include any dynamic prerequisites not set in the distribution metadata.
164             #pod
165             #pod Versions are reported based on the result of C<parse_version> from
166             #pod L<ExtUtils::MakeMaker>, which means prerequisite modules are not actually
167             #pod loaded (which avoids various edge cases with certain modules). Parse errors are
168             #pod reported as "undef". If a module is not installed, "missing" is reported
169             #pod instead of a version string.
170             #pod
171             #pod Additionally, if L<CPAN::Meta> is installed, unfulfilled required prerequisites
172             #pod are reported after the list of all versions based on either F<MYMETA>
173             #pod (preferably) or F<META> (fallback).
174             #pod
175             #pod =head1 CONFIGURATION
176             #pod
177             #pod =head2 include
178             #pod
179             #pod An C<include> attribute can be specified (multiple times) to add modules
180             #pod to the report. This can be useful if there is a module in the dependency
181             #pod chain that is problematic but is not directly required by this project.
182             #pod These modules will be listed in an "Other Modules" section at the end of
183             #pod the report.
184             #pod
185             #pod =head2 exclude
186             #pod
187             #pod An C<exclude> attribute can be specified (multiple times) to remove
188             #pod modules from the report (if you had a reason to do so).
189             #pod
190             #pod =head2 verify_prereqs
191             #pod
192             #pod When set, installed versions of all 'requires' prerequisites are verified
193             #pod against those specified. Defaults to true, but requires CPAN::Meta to be installed.
194             #pod
195             #pod =head2 version_extractor
196             #pod
197             #pod Specifies the module to use to extract each installed prerequisite's
198             #pod version. Defaults to L<ExtUtils::MakeMaker>, which is recommended for most
199             #pod situations. It can also be specified as L<Module::Metadata>, which can be
200             #pod useful if L<ExtUtils::MakeMaker>'s mechanism is too naive, or if
201             #pod L<ExtUtils::MakeMaker> is not already a prerequisite of the distribution.
202             #pod
203             #pod =head1 SEE ALSO
204             #pod
205             #pod Other Dist::Zilla::Plugins do similar things in slightly different ways that didn't
206             #pod suit my style and needs.
207             #pod
208             #pod =for :list
209             #pod * L<Dist::Zilla::Plugin::Test::PrereqsFromMeta> -- requires prereqs to be satisfied
210             #pod * L<Dist::Zilla::Plugin::Test::ReportVersions> -- bundles a copy of YAML::Tiny, reads prereqs only from META.yml, and attempts to load them with C<require>
211             #pod * L<Dist::Zilla::Plugin::ReportVersions::Tiny> -- static list only, loads modules with C<require>
212             #pod
213             #pod =cut
214              
215             =pod
216              
217             =encoding UTF-8
218              
219             =head1 NAME
220              
221             Dist::Zilla::Plugin::Test::ReportPrereqs - Report on prerequisite versions during automated testing
222              
223             =head1 VERSION
224              
225             version 0.029
226              
227             =head1 SYNOPSIS
228              
229             # in dist.ini
230             [Test::ReportPrereqs]
231             include = Acme::FYI
232             exclude = Acme::Dont::Care
233              
234             =head1 DESCRIPTION
235              
236             This L<Dist::Zilla> plugin adds a F<t/00-report-prereqs.t> test file and an accompanying
237             F<t/00-report-prereqs.dd> data file. It reports
238             the version of all modules listed in the distribution metadata prerequisites
239             (including 'recommends', 'suggests', etc.). However, any 'develop' prereqs
240             are not reported (unless they show up in another category).
241              
242             If a F<MYMETA.json> file exists and L<CPAN::Meta> is installed on the testing
243             machine, F<MYMETA.json> will be examined for prerequisites in addition, as it
244             would include any dynamic prerequisites not set in the distribution metadata.
245              
246             Versions are reported based on the result of C<parse_version> from
247             L<ExtUtils::MakeMaker>, which means prerequisite modules are not actually
248             loaded (which avoids various edge cases with certain modules). Parse errors are
249             reported as "undef". If a module is not installed, "missing" is reported
250             instead of a version string.
251              
252             Additionally, if L<CPAN::Meta> is installed, unfulfilled required prerequisites
253             are reported after the list of all versions based on either F<MYMETA>
254             (preferably) or F<META> (fallback).
255              
256             =for Pod::Coverage gather_files
257             mvp_multivalue_args
258             register_prereqs
259              
260             =head1 CONFIGURATION
261              
262             =head2 include
263              
264             An C<include> attribute can be specified (multiple times) to add modules
265             to the report. This can be useful if there is a module in the dependency
266             chain that is problematic but is not directly required by this project.
267             These modules will be listed in an "Other Modules" section at the end of
268             the report.
269              
270             =head2 exclude
271              
272             An C<exclude> attribute can be specified (multiple times) to remove
273             modules from the report (if you had a reason to do so).
274              
275             =head2 verify_prereqs
276              
277             When set, installed versions of all 'requires' prerequisites are verified
278             against those specified. Defaults to true, but requires CPAN::Meta to be installed.
279              
280             =head2 version_extractor
281              
282             Specifies the module to use to extract each installed prerequisite's
283             version. Defaults to L<ExtUtils::MakeMaker>, which is recommended for most
284             situations. It can also be specified as L<Module::Metadata>, which can be
285             useful if L<ExtUtils::MakeMaker>'s mechanism is too naive, or if
286             L<ExtUtils::MakeMaker> is not already a prerequisite of the distribution.
287              
288             =head1 SEE ALSO
289              
290             Other Dist::Zilla::Plugins do similar things in slightly different ways that didn't
291             suit my style and needs.
292              
293             =over 4
294              
295             =item *
296              
297             L<Dist::Zilla::Plugin::Test::PrereqsFromMeta> -- requires prereqs to be satisfied
298              
299             =item *
300              
301             L<Dist::Zilla::Plugin::Test::ReportVersions> -- bundles a copy of YAML::Tiny, reads prereqs only from META.yml, and attempts to load them with C<require>
302              
303             =item *
304              
305             L<Dist::Zilla::Plugin::ReportVersions::Tiny> -- static list only, loads modules with C<require>
306              
307             =back
308              
309             =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
310              
311             =head1 SUPPORT
312              
313             =head2 Bugs / Feature Requests
314              
315             Please report any bugs or feature requests through the issue tracker
316             at L<https://github.com/dagolden/Dist-Zilla-Plugin-Test-ReportPrereqs/issues>.
317             You will be notified automatically of any progress on your issue.
318              
319             =head2 Source Code
320              
321             This is open source software. The code repository is available for
322             public review and contribution under the terms of the license.
323              
324             L<https://github.com/dagolden/Dist-Zilla-Plugin-Test-ReportPrereqs>
325              
326             git clone https://github.com/dagolden/Dist-Zilla-Plugin-Test-ReportPrereqs.git
327              
328             =head1 AUTHOR
329              
330             David Golden <dagolden@cpan.org>
331              
332             =head1 CONTRIBUTORS
333              
334             =for stopwords Brendan Byrd Dave Rolsky Karen Etheridge Kent Fredric Randy Stauner Ricardo Signes Yanick Champoux
335              
336             =over 4
337              
338             =item *
339              
340             Brendan Byrd <Perl@ResonatorSoft.org>
341              
342             =item *
343              
344             Dave Rolsky <autarch@urth.org>
345              
346             =item *
347              
348             Karen Etheridge <ether@cpan.org>
349              
350             =item *
351              
352             Kent Fredric <kentfredric@gmail.com>
353              
354             =item *
355              
356             Randy Stauner <randy@magnificent-tears.com>
357              
358             =item *
359              
360             Ricardo Signes <rjbs@users.noreply.github.com>
361              
362             =item *
363              
364             Yanick Champoux <yanick@babyl.dyndns.org>
365              
366             =back
367              
368             =head1 COPYRIGHT AND LICENSE
369              
370             This software is Copyright (c) 2012 by David Golden.
371              
372             This is free software, licensed under:
373              
374             The Apache License, Version 2.0, January 2004
375              
376             =cut
377              
378             __DATA__
379             ___[ t/00-report-prereqs.t ]___
380             #!perl
381              
382             use strict;
383             use warnings;
384              
385             # This test was generated by Dist::Zilla::Plugin::Test::ReportPrereqs INSERT_VERSION_HERE
386              
387             use Test::More tests => 1;
388              
389             use VERSION_EXTRACTOR_MODULE;
390             use File::Spec;
391              
392             # from $version::LAX
393             my $lax_version_re =
394             qr/(?: undef | (?: (?:[0-9]+) (?: \. | (?:\.[0-9]+) (?:_[0-9]+)? )?
395             |
396             (?:\.[0-9]+) (?:_[0-9]+)?
397             ) | (?:
398             v (?:[0-9]+) (?: (?:\.[0-9]+)+ (?:_[0-9]+)? )?
399             |
400             (?:[0-9]+)? (?:\.[0-9]+){2,} (?:_[0-9]+)?
401             )
402             )/x;
403              
404             # hide optional CPAN::Meta modules from prereq scanner
405             # and check if they are available
406             my $cpan_meta = "CPAN::Meta";
407             my $cpan_meta_pre = "CPAN::Meta::Prereqs";
408             my $HAS_CPAN_META = eval "require $cpan_meta; $cpan_meta->VERSION('2.120900')" && eval "require $cpan_meta_pre"; ## no critic
409              
410             # Verify requirements?
411             my $DO_VERIFY_PREREQS = INSERT_VERIFY_PREREQS_CONFIG;
412              
413             sub _max {
414             my $max = shift;
415             $max = ( $_ > $max ) ? $_ : $max for @_;
416             return $max;
417             }
418              
419             sub _merge_prereqs {
420             my ($collector, $prereqs) = @_;
421              
422             # CPAN::Meta::Prereqs object
423             if (ref $collector eq $cpan_meta_pre) {
424             return $collector->with_merged_prereqs(
425             CPAN::Meta::Prereqs->new( $prereqs )
426             );
427             }
428              
429             # Raw hashrefs
430             for my $phase ( keys %$prereqs ) {
431             for my $type ( keys %{ $prereqs->{$phase} } ) {
432             for my $module ( keys %{ $prereqs->{$phase}{$type} } ) {
433             $collector->{$phase}{$type}{$module} = $prereqs->{$phase}{$type}{$module};
434             }
435             }
436             }
437              
438             return $collector;
439             }
440              
441             my @include = qw(
442             INSERT_INCLUDED_MODULES_HERE
443             );
444              
445             my @exclude = qw(
446             INSERT_EXCLUDED_MODULES_HERE
447             );
448              
449             # Add static prereqs to the included modules list
450             my $static_prereqs = do 'INSERT_DD_FILENAME_HERE';
451              
452             # Merge all prereqs (either with ::Prereqs or a hashref)
453             my $full_prereqs = _merge_prereqs(
454             ( $HAS_CPAN_META ? $cpan_meta_pre->new : {} ),
455             $static_prereqs
456             );
457              
458             # Add dynamic prereqs to the included modules list (if we can)
459             my ($source) = grep { -f } 'MYMETA.json', 'MYMETA.yml';
460             my $cpan_meta_error;
461             if ( $source && $HAS_CPAN_META
462             && (my $meta = eval { CPAN::Meta->load_file($source) } )
463             ) {
464             $full_prereqs = _merge_prereqs($full_prereqs, $meta->prereqs);
465             }
466             else {
467             $cpan_meta_error = $@; # capture error from CPAN::Meta->load_file($source)
468             $source = 'static metadata';
469             }
470              
471             my @full_reports;
472             my @dep_errors;
473             my $req_hash = $HAS_CPAN_META ? $full_prereqs->as_string_hash : $full_prereqs;
474              
475             # Add static includes into a fake section
476             for my $mod (@include) {
477             $req_hash->{other}{modules}{$mod} = 0;
478             }
479              
480             for my $phase ( qw(configure build test runtime develop other) ) {
481             next unless $req_hash->{$phase};
482             next if ($phase eq 'develop' and not $ENV{AUTHOR_TESTING});
483              
484             for my $type ( qw(requires recommends suggests conflicts modules) ) {
485             next unless $req_hash->{$phase}{$type};
486              
487             my $title = ucfirst($phase).' '.ucfirst($type);
488             my @reports = [qw/Module Want Have/];
489              
490             for my $mod ( sort keys %{ $req_hash->{$phase}{$type} } ) {
491             next if grep { $_ eq $mod } @exclude;
492              
493             my $want = $req_hash->{$phase}{$type}{$mod};
494             $want = "undef" unless defined $want;
495             $want = "any" if !$want && $want == 0;
496              
497             if ($mod eq 'perl') {
498             push @reports, ['perl', $want, $]];
499             next;
500             }
501              
502             my $req_string = $want eq 'any' ? 'any version required' : "version '$want' required";
503              
504             my $file = $mod;
505             $file =~ s{::}{/}g;
506             $file .= ".pm";
507             my ($prefix) = grep { -e File::Spec->catfile($_, $file) } @INC;
508              
509             if ($prefix) {
510             my $have = VERSION_EXTRACTION;
511             $have = "undef" unless defined $have;
512             push @reports, [$mod, $want, $have];
513              
514             if ( $DO_VERIFY_PREREQS && $HAS_CPAN_META && $type eq 'requires' ) {
515             if ( $have !~ /\A$lax_version_re\z/ ) {
516             push @dep_errors, "$mod version '$have' cannot be parsed ($req_string)";
517             }
518             elsif ( ! $full_prereqs->requirements_for( $phase, $type )->accepts_module( $mod => $have ) ) {
519             push @dep_errors, "$mod version '$have' is not in required range '$want'";
520             }
521             }
522             }
523             else {
524             push @reports, [$mod, $want, "missing"];
525              
526             if ( $DO_VERIFY_PREREQS && $type eq 'requires' ) {
527             push @dep_errors, "$mod is not installed ($req_string)";
528             }
529             }
530             }
531              
532             if ( @reports ) {
533             push @full_reports, "=== $title ===\n\n";
534              
535             my $ml = _max( map { length $_->[0] } @reports );
536             my $wl = _max( map { length $_->[1] } @reports );
537             my $hl = _max( map { length $_->[2] } @reports );
538              
539             if ($type eq 'modules') {
540             splice @reports, 1, 0, ["-" x $ml, "", "-" x $hl];
541             push @full_reports, map { sprintf(" %*s %*s\n", -$ml, $_->[0], $hl, $_->[2]) } @reports;
542             }
543             else {
544             splice @reports, 1, 0, ["-" x $ml, "-" x $wl, "-" x $hl];
545             push @full_reports, map { sprintf(" %*s %*s %*s\n", -$ml, $_->[0], $wl, $_->[1], $hl, $_->[2]) } @reports;
546             }
547              
548             push @full_reports, "\n";
549             }
550             }
551             }
552              
553             if ( @full_reports ) {
554             diag "\nVersions for all modules listed in $source (including optional ones):\n\n", @full_reports;
555             }
556              
557             if ( $cpan_meta_error || @dep_errors ) {
558             diag "\n*** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***\n";
559             }
560              
561             if ( $cpan_meta_error ) {
562             my ($orig_source) = grep { -f } 'MYMETA.json', 'MYMETA.yml';
563             diag "\nCPAN::Meta->load_file('$orig_source') failed with: $cpan_meta_error\n";
564             }
565              
566             if ( @dep_errors ) {
567             diag join("\n",
568             "\nThe following REQUIRED prerequisites were not satisfied:\n",
569             @dep_errors,
570             "\n"
571             );
572             }
573              
574             pass('Reported prereqs');
575              
576             # vim: ts=4 sts=4 sw=4 et: