File Coverage

blib/lib/Test/Dist.pm
Criterion Covered Total %
statement 127 156 81.4
branch 27 54 50.0
condition 6 16 37.5
subroutine 28 29 96.5
pod 1 1 100.0
total 189 256 73.8


line stmt bran cond sub pod time code
1             package Test::Dist;
2              
3 2     2   73299 use 5.006;
  2         6  
  2         75  
4 2     2   10 use strict;
  2         3  
  2         65  
5 2     2   8 use warnings;
  2         11  
  2         52  
6 2     2   9 use Test::Builder ();
  2         3  
  2         29  
7 2     2   15 use Test::More ();
  2         3  
  2         36  
8 2     2   976 use Test::Dist::Manifest;
  2         7  
  2         69  
9 2     2   8348 use Module::CPANTS::Analyse;
  2         3417828  
  2         17  
10              
11             =head1 NAME
12              
13             Test::Dist - Distribution kwalitee tests in one command
14              
15             =head1 VERSION
16              
17             Version 0.01
18              
19             =cut
20              
21             our $VERSION = '0.01';
22              
23             =head1 SYNOPSIS
24              
25             use Test::More;
26             use Test::Dist as => 0.01;
27             # using as => $version in use you may avoid breakage
28             # due to future tests additions to this module
29             use lib::abs '../lib';
30             chdir lib::abs::path('..');
31              
32             Test::Dist::dist_ok(
33             '+' => 1, # Add one more test to plan due to NoWarnings
34             run => 1, # Start condition. By default uses $ENV{TEST_AUTHOR}
35             skip => [qw(prereq)], # Skip prereq from testing
36             fixme => { # For options, see Test::Fixme
37             match => qr/FIXIT|!!!/, # Your own fixme patterns
38             },
39             kwalitee => {
40             req => [qw( has_separate_license_file has_example )], # Optional metrics, that you require to pass
41             },
42             );
43            
44             # Also, see examples and tests in this distribution
45              
46             =head1 FUNCTIONS
47              
48             =head2 dist_ok(%options)
49              
50             =head1 TESTS
51              
52             =over 4
53              
54             =item kwalitee
55              
56             Use L for testing kwalitee
57              
58             =item metayml
59              
60             Check C using L
61              
62             =item changes
63              
64             Check the correctness of C file
65              
66             =item fixme
67              
68             Test all modules and scripts using L
69              
70             =item useok
71              
72             Loading all modules by LC<::use:ok>
73              
74             =item syntax
75              
76             Checking all scripts by perl -c $file
77              
78             =item podcover
79              
80             Checking all modules for POD coverage using L
81              
82             =item prereq
83              
84             Checking prereq list using L
85              
86             =back
87              
88             =head1 OPTIONS
89              
90             =over 4
91              
92             =item '+' => 1|0
93              
94             How many tests add to plan
95              
96             =item run [ = $ENV{TEST_AUTHOR} ]
97              
98             Run condition for test
99              
100             =item skip => [ TESTS ]
101              
102             Skip some of tests
103              
104             =item kwalitee : { req => [ LIST ] }
105              
106             Force checking for some of optional metrics
107              
108             =item metayml : [ LIST ]
109              
110             For options see L
111              
112             =item fixme
113              
114             For options see L
115              
116             =item useok : { ... }
117              
118             useok => {
119             file_match => qr{^lib/.*\.pm$},
120             mod_skip => [ 'Module::Developed', qr{^Module::Developed::} ],
121             }
122              
123             =item syntax
124              
125             syntax => {
126             file_match => qr{^(lib|bin|script)/.*\.p(?:m|l|od)$},
127             file_skip => [ 'script/dummy.pl', qr{^bin/t/} ],
128             match => qr{!!!},
129             }
130              
131             =item podcover
132              
133             podcover => {
134             mod_match => qr{^Only::Some::Scope},
135             mod_skip => [ 'Only::Some::Scope::Developed', qr{^Only::Some::Scope::Developed::} ],
136             }
137              
138             =item prereq
139              
140             For options see L
141              
142             =back
143              
144             =cut
145              
146             my $Test = Test::Builder->new;
147             our %TESTS = (
148             '0.01' => [qw( kwalitee metayml changes fixme useok syntax podcover prereq )],
149             );
150             our %TEST_OK = map { $_ => 1 } @{ $TESTS{$VERSION} };
151             %TEST_OK or die "Test set no defined. This is an author error.";
152              
153             our @TESTS = (
154             [ kwalitee => [], sub {
155             my ($self,%args) = @_;
156             my %required = map { $_ => 1 } @{$args{req} || [] };
157             for my $gen ($self->_kwalitee_generators) {
158             #for my $gen (@{ Module::CPANTS::Kwalitee->new->generators() } ) {
159             #next if $gen =~ /Unpack/;
160             #next if $gen =~ /(^?:CpantsErrors|Distname|Prereq)$/;
161             #if ($gen eq 'Module::CPANTS::Kwalitee::Manifest') { $gen = 'Test::Dist::Manifest' }
162             for my $indicator (@{ $gen->kwalitee_indicators() }) {
163             next if $indicator->{needs_db};
164             my $test = $indicator->{name};
165             next if $test =~ /(?:debian|fedora)/;
166             next if $test =~ /(?:no_generated_files|extracts_nicely|extractable)/; # Not worked within source
167             $self->_queue(sub {
168             #return $Test->skip("OS-oriented metric") if $test =~ /(?:debian|fedora)/;
169             #return $Test->skip("Only for distribution") if $test =~ /(?:no_generated_files|extracts_nicely|extractable)/; # Not worked within source
170             my $ok = $indicator->{code}->( $self->{d} );
171             {
172 2     2   703 no strict 'refs';
  2         4  
  2         3897  
173             local ${ 'TO'.'DO' } = ($indicator->{is_experimental} ? 'Experimental' : 'Optional').' metric'
174             if !$required{$test} and ( $indicator->{is_experimental} or $test =~ /^(?:
175             has_separate_license_file | has_example |
176             uses_test_nowarnings | is_prereq
177             )$/x );
178             $Test->ok( $ok, $test . (!$ok ? " (from: $gen)" : '') )
179             or map { $_ and ref $_ ? map { $Test->diag($_) } @$_ : $Test->diag($_) }
180             #$generator,
181             @{ $indicator }{qw( error remedy )},
182             $self->{d}{error}{ $test };
183             }
184             });
185             }
186             }
187            
188             }],
189             [ metayml => 'Test::YAML::Meta' => sub {
190             my $self = shift;
191             my ($file,$vers,$msg) = @_;
192             $file ||= 'META.yml';
193             $msg ||= "$file meets specification";
194             my $yaml;
195             $self->_queue(sub {
196             $yaml = Test::YAML::Meta::yaml_file_ok($file);
197             });
198             $self->_queue(sub {
199             if ($yaml) {
200             my %hash;
201             $hash{spec} = $vers if($vers);
202             $hash{yaml} = $yaml;
203             my $spec = Test::YAML::Meta::Version->new(%hash);
204             if(my $result = $spec->parse()) {
205             $self->_ok(0,$msg);
206             $Test->diag(" ERR: $_") for( $spec->errors );
207             } else {
208             $Test->ok(1,$msg);
209             }
210             } else {
211             $Test->_ok(0, $msg);
212             }
213             });
214             }],
215             [ changes => [] => sub {
216             my $self = shift;
217             my $msg = "Check Changes";
218             if (exists $self->{d}{file_changelog} and -e $self->{d}{file_changelog}) {
219             if (my $version = $self->{d}{meta_yml}{version}) {
220             $msg .= " $version";
221             $self->_queue(sub {
222             my $file = $self->{d}{file_changelog};
223             open(my $f, '<', $file) or return $self->_ok(0, $msg, "Could not open file ($file)");
224             my $found = 0;
225             my @not_found;
226             while (<$f>) {
227             chomp;
228             if (/^\d/) { # Common
229             my ($cvers, $date) = split(/\s+/, $_, 2);
230             if ($version eq $cvers) {
231             $found = $_;
232             last;
233             } else {
234             push(@not_found, "$cvers");
235             }
236             }
237             elsif (/^\s+version: ([\d.]+)$/) { # YAML
238             if ($version eq $1) {
239             $found = $_;
240             last;
241             } else {
242             push(@not_found, "$1");
243             }
244             }
245             elsif (/^\* ([\d.]+)$/) { # Apocal
246             if ($version eq $1) {
247             $found = $_;
248             last;
249             } else {
250             push(@not_found, "$1");
251             }
252             } elsif (/^Version ([\d.]+)($|[:,[:space:]])/) { # Plain "Version N"
253             if ($version eq $1) {
254             $found = $_;
255             last;
256             } else {
257             push(@not_found, "$1");
258             }
259             }
260             }
261             close($f);
262             if ($found) {
263             $Test->ok(1,$msg);
264             } else {
265             $Test->ok(1,$msg. " not found.");
266             if (@not_found) {
267             $Test->diag(qq(expecting version $version, found versions: ). join(', ', @not_found));
268             } else {
269             $Test->diag(qq(expecting version $version, But no versions where found in the Changes file.));
270             }
271             }
272             });
273             } else {
274             $self->_queue(sub { $self->_ok(0, $msg, "No dist version" ); });
275             }
276             } else {
277             $self->_queue(sub { $self->_ok(0, $msg, "No Changelog found"); });
278             }
279             }],
280            
281             [ fixme => 'Test::Fixme' => sub {
282             my $self = shift;
283             my %args = @_;
284             $args{match} = 'FIX'.'ME|TO'.'DO' unless defined $args{match} && length $args{match};
285             $args{file_match} = $args{filename_match} if defined $args{filename_match} and !defined $args{file_match};
286             $args{file_match} = qr{^(lib|bin|script)/.*\.p(?:m|l|od)$} unless defined $args{file_match};
287             my @files = $self->_filelist(%args);
288             for my $file (@files) {
289             $self->_queue(sub {
290             my $results = Test::Fixme::scan_file( file => $file, match => $args{match} );
291             if ( !$results or @$results == 0 ) {
292             $self->_ok( 1, "Fixme '$file'" );
293             }
294             else {
295             $self->_ok( 0, "Fixme '$file'", Test::Fixme::format_file_results($results) );
296             }
297             });
298             }
299             } ],
300             [ useok => [], sub {
301             my $self = shift;
302             my %args = @_;
303             my @files = $self->_modlist(%args);
304             for my $file (@files) {
305             $self->_queue(sub {
306 1     1   203 Test::More::use_ok($file);
  1     1   2  
  1         2  
  1         7  
  1         72  
  1         2  
  1         2  
  1         15  
307             });
308             }
309             if (!@files) {
310             $self->_queue(sub { $Test->skip("Found no modules for use_ok check"); });
311             }
312             }],
313             [ syntax => [], sub {
314             my $self = shift;
315             my %args = @_;
316             $args{file_match} = qr{^(?:bin|script)/.+} unless defined $args{file_match};
317             my @files = $self->_filelist(%args);
318             for my $file (@files) {
319             $self->_queue(sub {
320             my $res = `$^X -c '$file' 2>&1`;
321             my $rc = $? >> 8;
322             $self->_ok($rc == 0, "syntax $file", $rc ? ("Exitcode = $rc",$res) : ());
323             });
324             }
325             if (!@files) {
326             $self->_queue(sub { $Test->skip("Found no files for syntax check"); });
327             }
328             }],
329             [ podcover => ['Test::Pod::Coverage 1.08','Pod::Coverage 0.18'], sub {
330             my $self = shift;
331             my %args = @_;
332             my @files = $self->_modlist(%args);
333             for my $file (@files) {
334             $self->_queue(sub {
335             Test::Pod::Coverage::pod_coverage_ok($file, "POD coverage on $file");
336             });
337             }
338             if (!@files) {
339             $self->_queue(sub { $Test->skip("Found no modules for pod-coverage check"); });
340             }
341             }],
342             [ prereq => 'Test::Prereq', sub {
343             my $self = shift;
344             my @args = @_;
345             $self->_queue(sub {
346             $Test->diag("Runnkig Test::Prereq. Please, wait a while...");
347             local $0 = 'Makefile.PL'; # Hack
348             local *STDOUT;
349             local *STDERR;
350             local $ENV{PERL5LIB} = 'lib';
351             my $old_gff = \&Test::Prereq::_get_from_file;
352             my %uses;
353 2     2   15 no warnings 'redefine';
  2         4  
  2         1831  
354             local *Test::Prereq::_get_from_file = sub {
355             my( $class, $file ) = @_;
356             my $module = Module::Info->new_from_file( $file );
357             $module->die_on_compilation_error(1);
358             my @used = eval{ $module->modules_used };
359             #push @{ $uses{$_} ||= [] }, $file;
360             $Test->diag("$@") if $@;
361             goto &$old_gff;
362             };
363             local *Test::Prereq::_get_dist_modules = sub {
364             [ map { $_->{in_lib} ? ($_->{module}) : () } @{ $self->{d}{modules} } ]
365             };
366             local *Test::Prereq::_get_loaded_modules = sub {
367             my $class = shift;
368             my @found;
369             for my $file (
370             grep {
371             m{^(?:lib/.+\.pm|t/.+\.t|script/.+)$}
372             } @{$self->{d}{files_array}}
373             ) {
374             my $used = $class->_get_from_file( $file );
375             #warn "Found @{$used} from $file";
376             push @found, @$used;
377             }
378             return { map { $_ => 1 } @found };
379             };
380             {
381             local $SIG{__WARN__} = sub {};
382             Test::Prereq::prereq_ok(@args);
383             }
384             });
385             } ],
386             );
387              
388             sub _matchsub {
389 3     3   4 my $self = shift;
390 3         3 my $match = shift;
391 3 50   5   12 $match or return sub { 0 };
  5         19  
392 0         0 my @match_qr;
393             my %match_eq;
394 0         0 for ( @{ $match } ) {
  0         0  
395 0 0       0 if (UNIVERSAL::isa($_,"Regexp")) {
396 0         0 push @match_qr, $_;
397             } else {
398 0         0 $match_eq{$_} = 1;
399             }
400             }
401             return sub {
402 0 0   0   0 return 1 if $match_eq{$_[0]};
403 0         0 for (@match_qr) {
404 0 0       0 return 1 if $_[0] =~ $_;
405             }
406 0         0 return 0;
407 0         0 };
408             }
409              
410             sub _filelist {
411 2     2   3 my $self = shift;
412 2         3 my %args = @_;
413 2         8 my $skip = $self->_matchsub(delete $args{file_skip});
414 2 100 66     3 my @files = ( map { ( $_ =~ $args{file_match} && !$skip->($_) ) ? ($_) : () } @{ $self->{d}{files_array} } );
  58         225  
  2         6  
415             }
416              
417             sub _modlist {
418 1     1   2 my $self = shift;
419 1         1 my %args = @_;
420 1 50       8 $args{file_match} = qr{^lib/.*\.pm$} unless defined $args{file_match};
421 1         4 my @files = $self->_filelist(%args);
422 1 50       7 $args{mod_match} = qr{.+} unless defined $args{mod_match};
423 1         4 my $skip = $self->_matchsub(delete $args{mod_skip});
424 2         5 @files = map {
425 1         3 my $x = $_;
426 2         6 $x =~ s{^lib/}{};
427 2         7 $x =~ s/\.pm$//;
428 2         6 $x =~ s|/|::|g;
429 2 50       5 $skip->($x) ? () : ($x);
430             } @files;
431 1         6 return @files;
432             }
433              
434             sub import {
435 3     3   18 my $me = shift;
436 3         9 my $cl = caller;
437 2     2   14 no strict 'refs';
  2         2  
  2         2353  
438 3         8 *{$cl.'::dist_ok'} = \&dist_ok;
  3         14  
439 3 50 33     56 if (@_ and $_[0] eq 'as') {
440 0         0 shift;
441 0         0 my $version = shift;
442 0         0 %TEST_OK = map { $_ => 1 } @{ $TESTS{$version} };
  0         0  
  0         0  
443 0 0       0 %TEST_OK or die "$me not defined test set for version $version";
444 0 0       0 $Test->diag("Using $me $VERSION as of $version") if $VERSION ne $version;
445             }
446             }
447              
448             sub dist_ok {
449 1     1 1 197 my $self = bless {};
450 1         12 my %args = (
451             run => $ENV{TEST_AUTHOR},
452             @_,
453             );
454 1 50       2 my %skip = map { $_ => 1 } @{ delete($args{skip}) || [] };
  1         5  
  1         7  
455 1         8 $self->{skip} = \%skip;
456 1         3 $self->{args} = \%args;
457 1         3 $self->{dist} = '.';
458 1         5 $self->_init;
459 1 50       1 for (@{ $self->{testqueue} || [] }) {
  1         6  
460 31         10844 $_->();
461             }
462             }
463              
464             sub _kwalitee_generators {
465 2     2   5 my $self = shift;
466 2         6 my @gen;
467 2         4 for my $gen (@{ Module::CPANTS::Kwalitee->new->generators() } ) {
  2         16  
468 32 50       20432 next if $gen =~ /Unpack/;
469 32 100       141 next if $gen =~ /(^?:CpantsErrors|Distname|Prereq)$/;
470 26 100       44 if ($gen eq 'Module::CPANTS::Kwalitee::Manifest') { $gen = 'Test::Dist::Manifest' }
  2         4  
471 26         36 push @gen, $gen;
472             }
473 2         22 @gen;
474             }
475              
476             sub _init {
477 1     1   2 my $self = shift;
478 1 50 33     8 if (!$Test->has_plan and !$self->{args}{run}) {
479 0 0       0 $Test::NoWarnings::do_end_test = 0 if $INC{'Test/NoWarnings.pm'};
480 0         0 $Test->plan( skip_all => "Run condition not met" );
481 0         0 return;
482             };
483 1         18 $self->{testqueue} = [];
484 1         16 $self->{a} = Module::CPANTS::Analyse->new({
485             distdir => $self->{dist},
486             dist => $self->{dist},
487             });
488 1         382924 for my $gen ($self->_kwalitee_generators) {
489 13         121801 local $^W;
490 13         156 $gen->analyse($self->{a});
491             }
492 1         13 $self->{d} = $self->{a}->d();
493 1         10 my $tests = 0;
494 1         13 for (@TESTS) {
495 8         29 my ($key,$use,$code) = @$_;
496 8 50       22 next unless $TEST_OK{$key};
497 8 100       22 next if $self->{skip}{$key};
498 7 100       22 my @use = ref $use ? @$use : $use;
499 7         13 my $req = join '; ', map { "use $_ ()" } @use;
  4         12  
500             #warn "loading: $req";
501 7 100   1   375 if (eval "$req; 1") {
  1     1   387  
  0     1   0  
  0         0  
  1         334  
  0         0  
  0         0  
  1         322  
  0         0  
  0         0  
502 0         0 $code->($self,
503             $self->{args}{$key} ? (
504 2         12 ref $self->{args}{$key} eq 'ARRAY' ? @{ $self->{args}{$key} } :
505 4 50       29 ref $self->{args}{$key} eq 'HASH' ? %{ $self->{args}{$key} } :
    50          
    100          
506             $self->{args}{$key}
507             ) : ()
508             );
509             } else {
510             $self->_queue(sub {
511 3     3   33 $self->_skip(join(", ",@use)." required for testing $key");
512 3         16 });
513             }
514            
515             }
516 1         17 $Test->plan( tests =>
517             $tests
518 1 50 50     8 + @{ $self->{testqueue} }
519             + ( $self->{args}{'+'} || 0 )
520             ) unless $Test->has_plan;
521 1         571 return;
522             }
523              
524             sub _queue {
525 31     31   34 my $self = shift;
526 31         29 my $code = shift;
527 31         28 push @{ $self->{testqueue} }, $code;
  31         66  
528 31         70 return;
529             }
530              
531             sub _skip {
532 3     3   5 my( $self, $why, $n ) = @_;
533 3   50     20 $n ||= 1;
534 3         24 $Test->skip($why) for 1..$n;
535             }
536              
537             sub _ok {
538 1     1   25 my( $self, $ok, $name, @message ) = @_;
539 1         46 $Test->ok( $ok, $name );
540 1         1264 $Test->diag( $_ ) for @message;
541             }
542              
543             END {
544 2     2   1843 for () {
545 0 0 0     0 unlink $_ or $! and print STDERR "#! unlink $_: $!\n";
546             }
547             }
548              
549             =head1 AUTHOR
550              
551             Mons Anderson, C<< >>
552              
553             =head1 BUGS
554              
555             Please report any bugs or feature requests to C, or through
556             the web interface at L. I will be notified, and then you'll
557             automatically be notified of progress on your bug as I make changes.
558              
559             =head1 ACKNOWLEDGEMENTS
560              
561             Thanks to
562              
563             =over 4
564              
565             =item * B for L
566              
567             =item * B for L
568              
569             =item * B for L
570              
571             =item * B for L
572              
573             =item * B for L
574              
575             =item * B for L
576              
577             =back
578              
579             =head1 COPYRIGHT & LICENSE
580              
581             Copyright 2009 Mons Anderson, all rights reserved.
582              
583             This program is free software; you can redistribute it and/or modify it
584             under the same terms as Perl itself.
585              
586              
587             =cut
588              
589             1; # End of Test::Dist