File Coverage

blib/lib/CPAN/YACSmoke.pm
Criterion Covered Total %
statement 68 324 20.9
branch 3 124 2.4
condition 0 84 0.0
subroutine 23 40 57.5
pod 6 10 60.0
total 100 582 17.1


line stmt bran cond sub pod time code
1             =head1 NAME
2            
3             CPAN::YACSmoke - Yet Another CPAN Smoke Tester
4            
5             =head1 SYNOPSIS
6            
7             perl -MCPAN::YACSmoke -e test
8            
9             =head1 DESCRIPTION
10            
11             This module uses the backend of L to run tests on modules
12             recently uploaded to CPAN and post results to the CPAN Testers list.
13            
14             It will create a database file in the F<.cpanplus> directory, which it
15             uses to track tested distributions. This information will be used to
16             keep from posting multiple reports for the same module, and to keep
17             from testing modules that use non-passing modules as prerequisites.
18            
19             If it is given multiple versions of the same distribution to test, it
20             will test the most recent version only. If that version fails, then
21             it will test a previous version.
22            
23             By default it uses CPANPLUS configuration settings.
24            
25             =cut
26            
27             package CPAN::YACSmoke;
28            
29 6     6   142422 use 5.006001;
  6         21  
  6         225  
30 6     6   33 use strict;
  6         10  
  6         187  
31 6     6   28 use warnings;
  6         13  
  6         258  
32            
33 6     6   6223 use CPANPLUS::Backend 0.051;
  6         3213006  
  6         239  
34 6     6   73 use CPANPLUS::Configure;
  6         41  
  6         131  
35 6     6   34 use CPANPLUS::Error;
  6         11  
  6         436  
36            
37 6     6   36 use File::Basename;
  6         11  
  6         403  
38 6     6   37 use File::HomeDir qw( home );
  6         14  
  6         610  
39 6     6   6494 use File::Spec::Functions qw( splitpath catfile );
  6         5871  
  6         437  
40 6     6   5181 use LWP::Simple;
  6         441531  
  6         64  
41 6     6   2791 use POSIX qw( O_CREAT O_RDWR ); # for SDBM_File
  6         13  
  6         59  
42 6     6   11708 use Regexp::Assemble;
  6         113636  
  6         251  
43 6     6   5832 use SDBM_File;
  6         4316  
  6         272  
44 6     6   5228 use Sort::Versions;
  6         4647  
  6         734  
45 6     6   37 use URI;
  6         14  
  6         158  
46 6     6   5019 use Module::Pluggable search_path => ["CPAN::YACSmoke::Plugin"];
  6         40065  
  6         95  
47 6     6   483 use Carp;
  6         12  
  6         356  
48 6     6   7365 use Config::IniFiles;
  6         84143  
  6         925  
49            
50             # use YAML 'Dump';
51            
52             require Test::Reporter;
53            
54             our $VERSION = '0.03';
55             $VERSION = eval $VERSION;
56            
57             require Exporter;
58            
59             our @ISA = qw( Exporter );
60             our %EXPORT_TAGS = (
61             'all' => [ qw( mark test excluded ) ],
62             'default' => [ qw( mark test excluded ) ],
63             );
64            
65             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
66             our @EXPORT = ( @{ $EXPORT_TAGS{'default'} } );
67            
68             # TODO: option to change default names
69            
70 6     6   71 use constant DATABASE_FILE => 'cpansmoke.dat';
  6         14  
  6         400  
71 6     6   60 use constant CONFIG_FILE => 'cpansmoke.ini';
  6         12  
  6         24457  
72            
73             my $extn = qr/(?:\.(?:tar\.gz|tgz|zip))/; # supported archive extensions
74            
75            
76             {
77             my %Checked;
78             my $TiedObj;
79            
80             # We use the TiedObj flag instead of tied(%Checked) because the
81             # function creates an additional reference in the scope of an
82             # if (tied %Checked) { ... } which causes a warning etc.
83            
84             sub connect_db {
85 0     0 0 0 my $self = shift;
86 0   0     0 my $filename = shift || catfile($self->basedir(), DATABASE_FILE);
87 0 0       0 if ($TiedObj) {
88             # error("Already connected to the database!");
89             } else {
90 0         0 $TiedObj = tie %Checked, 'SDBM_File', $filename, O_CREAT|O_RDWR, 0644;
91 0         0 $self->{checked} = \%Checked;
92 0         0 $self->_debug("Connected to database ($filename).");
93             }
94             }
95            
96             sub disconnect_db {
97 1     1 0 2 my $self = shift;
98            
99 1 50       15 if ($TiedObj) {
100 0         0 $TiedObj = undef;
101 0         0 $self->{checked} = undef;
102 0         0 untie %Checked;
103 0         0 $self->_debug("Disconnected from database.");
104             # } else {
105             # error("Not connected to the database!");
106             }
107             }
108            
109             my $CONF = CPANPLUS::Configure->new();
110             sub connect_configure {
111 0     0 0 0 return $CONF;
112             }
113            
114             my $CpanPlus;
115            
116             sub connect_cpanplus {
117 0     0 0 0 my $self = shift;
118 0 0       0 return $self->{cpan} = $CpanPlus if ($CpanPlus);
119            
120 0         0 my $re = new Regexp::Assemble;
121 0         0 $re->add( @{$self->{exclude_dists}} );
  0         0  
122            
123 0         0 $CpanPlus = CPANPLUS::Backend->new();
124            
125 0 0       0 if ($CPANPLUS::Backend::VERSION >= 0.052) {
126            
127             # TODO: if PASS included skipped tests, add a comment
128            
129             $CpanPlus->_register_callback(
130             name => 'munge_test_report',
131             code => sub {
132 0     0   0 my $mod = shift;
133 0   0     0 my $report = shift || "";
134 0         0 $report .=
135             "\nThis report was machine-generated by CPAN::YACSmoke $VERSION.\n";
136 0         0 return $report;
137             },
138 0         0 );
139             }
140            
141             # BUG: this callback does not seem to get called consistently, if at all.
142            
143             $CpanPlus->_register_callback(
144             name => 'install_prerequisite',
145             code => sub {
146 0     0   0 my $mod = shift;
147 0         0 my $root;
148 0 0       0 if ($mod->package =~ /^(.+)$extn$/) {
149 0         0 $root = $1;
150             }
151             else {
152 0         0 error("Cannot handle ".$mod->package);
153 0         0 return;
154             }
155            
156 0 0       0 unless ($TiedObj) {
157 0         0 croak "Not connected to database!";
158             }
159 0         0 while (my $arg = shift) {
160 0         0 $arg->package =~ m/^(.+)$extn$/;
161 0         0 my $package = $1;
162            
163             # BUG: Exclusion does not seem to work for prereqs.
164             # Sometimes it seems that the install_prerequisite
165             # callback is not even called! Need to investigate.
166            
167 0 0       0 if ($package =~ $re->re) { # prereq on excluded list
168 0         0 msg("Prereq $package is excluded");
169 0         0 return;
170             }
171            
172 0         0 my $checked = $Checked{$package};
173 0 0 0     0 if (defined $checked &&
174             $checked =~ /aborted|fail|unknown|na|ungraded/ ) {
175            
176 0 0       0 if ($self->{ignore_bad_prereqs}) {
177 0         0 msg("Known uninstallable prereqs $package - may have problems\n");
178             } else {
179 0         0 msg("Known uninstallable prereqs $package - aborting install\n");
180 0         0 $Checked{$root} = "aborted";
181 0         0 return;
182             }
183             }
184             }
185 0         0 return 1;
186             },
187 0         0 );
188            
189             $CpanPlus->_register_callback(
190             name => 'send_test_report',
191             code => sub {
192            
193 0 0   0   0 unless ($TiedObj) {
194 0         0 exit error("Not connected to database!");
195             }
196 0         0 my $mod = shift;
197 0         0 my $grade = lc shift;
198 0 0       0 if ($mod->{package} =~ /^(.+)$extn$/) {
199 0         0 my $package = $1;
200 0         0 my $checked = $Checked{$package};
201            
202             # TODO: option to report only passing tests
203            
204 0 0       0 return unless ($self->{cpantest});
205            
206 0 0 0     0 return if (defined $checked && (
      0        
207             ($checked eq 'aborted' && $grade ne 'pass') ||
208             ($checked eq 'unknown' && $grade eq 'unknown') ||
209             ($checked eq 'ungraded' && $grade eq 'fail') ||
210             ($checked =~ /pass|na/) ||
211             ($checked eq 'fail' && $grade =~ /unknown|na|fail/)));
212            
213 0         0 $Checked{$package} = $grade;
214            
215 0   0     0 return ((!$self->{report_pass_only}) || ($grade eq 'pass'));
216            
217             } else {
218 0         0 error("Unable to parse package information\n");
219 0         0 return;
220             }
221             },
222 0         0 );
223            
224             $CpanPlus->_register_callback(
225             name => 'edit_test_report',
226 0     0   0 code => sub { return; },
227 0         0 );
228            
229 0         0 return $self->{cpan} = $CpanPlus;
230             }
231             }
232            
233             my @CONFIG_FIELDS = qw(
234             verbose debug force cpantest
235             recent_list_age ignore_cpanplus_bugs fail_max
236             exclude_dists test_max audit_log
237             ignore_bad_prereqs report_pass_only
238             );
239            
240             my @CPANPLUS_FIELDS = qw(
241             verbose debug force cpantest
242             prereqs skiptest
243            
244             );
245            
246            
247             =head1 OBJECT INTERFACE
248            
249             =over 4
250            
251             =item new( [ %config ] )
252            
253             The object interface is created normally through the test() or mark()
254             functions of the procedural interface. However, it can be accessed
255             with a set of configuration settings to extend the capabilities of
256             the package.
257            
258             Configuration settings are:
259            
260             verbose
261             debug
262             force
263             cpantest
264             report_pass_only
265             prereqs
266             ignore_cpanplus_bugs
267             ignore_bad_prereqs
268             fail_max
269             exclude_dists
270             test_max
271            
272             list_from - List plugin required, default Recent
273            
274             recent_list_age - used with the Recent plugin
275             recent_list_path - used with the Recent plugin
276             mailbox - used with the Outlook plugin
277             nntp_id - used with the NNTP plugin
278             webpath - used with the WebList plugin
279            
280             audit_log - log file to write progress to
281            
282             config_file - an INI file with the above settings
283            
284             All settings can use defaults. With regards to the last setting,
285             the INI file should contain one setting per line, except the values
286             for the exclude_dists setting, which are laid out as:
287            
288             [CONFIG]
289             exclude_dists=<
290             mod_perl
291             HERE
292            
293             The above would then ignore any distribution that include the string
294             'mod_perl' in its name. This is useful for distributions which use
295             external C libraries, which are not installed, or for which testing
296             is problematic.
297            
298             The setting 'test_max' is used to restrict the number of distributions
299             tested in a single run. As some distributions can take some time to be
300             tested, it may be more suitable to run in small batches at a time. The
301             default setting is 100 distributions.
302            
303             =back
304            
305             =cut
306            
307             sub new {
308 0   0 0 1 0 my $class = shift || __PACKAGE__;
309            
310             ## Ensure CPANPLUS knows we automated. (Q: Should we use Env::C to
311             ## set this instead?)
312            
313 0         0 $ENV{AUTOMATED_TESTING} = 1;
314            
315 0         0 my $conf = connect_configure();
316            
317             ## set internal defaults
318 0         0 my $self = {
319             conf => $conf,
320             checked => undef,
321             ignore_cpanplus_bugs => ($CPANPLUS::Backend::VERSION >= 0.052),
322             fail_max => 3, # max failed versions to try
323             exclude_dists => [ ], # Regexps to exclude
324             test_max => 100, # max distributions per run
325             };
326            
327 0         0 bless $self, $class;
328            
329             ## set from CPANPLUS defaults
330 0         0 foreach my $field (@CPANPLUS_FIELDS) {
331 0   0     0 $self->{$field} = $conf->get_conf($field) || 0;
332             }
333            
334            
335             ## force overide of default settings
336 0         0 $self->{skiptest} = 0;
337 0         0 $self->{prereqs} = 2; # force to ask callback
338            
339 0         0 my %config = @_;
340            
341             ## config_file is an .ini file
342            
343 0   0     0 $config{config_file} ||= catfile($self->basedir(), CONFIG_FILE);
344            
345 0 0 0     0 if($config{config_file} && -r $config{config_file}) {
346 0         0 my $cfg = Config::IniFiles->new(-file => $config{config_file});
347 0         0 foreach my $field (@CONFIG_FIELDS) {
348 0         0 my $val = $cfg->val( 'CONFIG', $field );
349 0 0       0 $self->{$field} = $val if(defined $val);
350             }
351 0         0 my @list = $cfg->val( 'CONFIG', 'exclude_dists' );
352 0 0       0 $self->{exclude_dists} = [ @list ] if(@list);
353             }
354            
355 0 0       0 if ($self->{audit_log}) {
356 0         0 my ($vol, $path, $file) = splitpath $self->{audit_log};
357 0 0 0     0 unless ($vol || $path) {
358 0         0 $self->{audit_log} = catfile($self->basedir(), $file);
359             }
360             }
361            
362            
363             ## command line switches override
364 0         0 foreach my $field (@CONFIG_FIELDS, 'audit_cb') {
365 0 0       0 if (exists $config{$field}) {
366 0         0 $self->{$field} = $config{$field};
367             }
368             }
369            
370             ## reset CPANPLUS defaults
371 0         0 foreach my $field (@CPANPLUS_FIELDS) {
372 0         0 $conf->set_conf($field => $self->{$field});
373             }
374            
375 0 0       0 $self->{test_max} = 0 if($self->{test_max} < 0); # sanity check
376            
377            
378             ## determine the data source plugin
379            
380 0   0     0 $config{list_from} ||= 'Recent';
381 0         0 my $plugin;
382 0         0 my @plugins = $self->plugins();
383 0         0 for(@plugins) {
384 0 0       0 $plugin = $_ if($_ =~ /$config{list_from}/);
385             }
386            
387 0 0       0 croak("no plugin available of that name\n") unless($plugin);
388 0         0 eval "CORE::require $plugin";
389 0 0       0 croak "Couldn't require $plugin : $@" if $@;
390 0         0 $config{smoke} = $self;
391 0         0 $self->{plugin} = $plugin->new(\%config);
392            
393 0         0 $self->connect_db();
394 0         0 $self->connect_cpanplus();
395            
396 0         0 return $self;
397             }
398            
399            
400             sub DESTROY {
401 1     1   6078 my $self = shift;
402 1         7 $self->_audit("Disconnecting from database");
403 1         7 $self->disconnect_db();
404             }
405            
406             =head2 METHODS
407            
408             =over 4
409            
410             =item homedir
411            
412             Obtains the users home directory
413            
414             =cut
415            
416             # TODO: use CPANPLUS function
417            
418             sub homedir {
419 0     0 1 0 my $self = shift;
420 0 0       0 return $self->{homedir} = shift if (@_);
421            
422 0 0       0 unless (defined $self->{homedir}) {
423 0 0       0 if ($^O eq "MSWin32") { # bug in File::HomeDir <= 0.06
424 0   0     0 $self->{homedir} = $ENV{HOME} ||
425             ($ENV{HOMEDRIVE}.$ENV{HOMEPATH}) ||
426             $ENV{USERPROFILE} ||
427             home();
428             } else {
429 0         0 $self->{homedir} = home();
430             }
431             }
432 0         0 $self->_audit("homedir = " . $self->{homedir});
433 0         0 return $self->{homedir};
434             }
435            
436             =item basedir
437            
438             Obtains the base directory for downloading and testing distributions.
439            
440             =back
441            
442             =cut
443            
444             sub basedir {
445 0     0 1 0 my $self = shift;
446 0 0       0 return $self->{basedir} = shift if (@_);
447            
448 0 0       0 unless (defined $self->{basedir}) {
449 0   0     0 $self->{basedir} = $self->{conf}->get_conf("base") || $self->homedir();
450             }
451 0         0 return $self->{basedir};
452             }
453            
454             sub _remove_excluded_dists {
455 0     0   0 my $self = shift;
456 0         0 my @dists = ( );
457 0         0 my $removed = 0;
458            
459 0         0 my $re = new Regexp::Assemble;
460 0         0 $re->add( @{ $self->{exclude_dists} } );
  0         0  
461            
462 0         0 while (my $dist = shift) {
463 0 0       0 if ($dist =~ $re->re) {
464 0         0 chomp($dist);
465 0         0 $self->_track("Excluding $dist");
466 0         0 $removed = 1;
467             } else {
468 0         0 push @dists, $dist;
469             }
470             }
471 0 0       0 $self->_audit('') if($removed);
472 0         0 return @dists;
473             }
474            
475             sub _build_path_list {
476 0     0   0 my $self = shift;
477 0         0 my $ignored = 0;
478            
479 0         0 my %paths = ( );
480 0         0 while (my $line = shift) {
481 0 0       0 if ($line =~ /^(.*)\-(.+)(\.tar\.gz)$/) {
    0          
482 0         0 my $dist = $1;
483 0         0 my @dirs = split /\/+/, $dist;
484 0         0 my $ver = $2;
485            
486             # due to rt.cpan.org bugs #11093, #11125 in CPANPLUS
487            
488 0 0 0     0 if ($self->{ignore_cpanplus_bugs} || (
      0        
489             (@dirs == 4) && ($ver =~ /^[\d\.\_]+$/)) ) {
490            
491 0 0       0 if (exists $paths{$dist}) {
492 0         0 unshift @{ $paths{$dist} }, $ver;
  0         0  
493             } else {
494 0         0 $paths{$dist} = [ $ver ];
495             }
496            
497             } else {
498 0         0 $self->_track("Ignoring $dist-$ver (due to CPAN+ bugs)");
499 0         0 $ignored = 1;
500             }
501            
502             # check for previously parsed package string
503             } elsif ($line =~ /^(.*)\-(.+)$/) {
504 0         0 my $dist = $1;
505 0         0 my @dirs = split /\/+/, $dist;
506 0         0 my $ver = $2;
507            
508 0 0       0 if (@dirs == 1) { # previously parsed
509 0 0       0 if (exists $paths{$dist}) {
510 0         0 unshift @{ $paths{$dist} }, $ver;
  0         0  
511             } else {
512 0         0 $paths{$dist} = [ $ver ];
513             }
514             }
515             }
516             }
517 0 0       0 $self->_audit('') if($ignored);
518 0         0 return %paths;
519             }
520            
521             =head1 PROCEDURAL INTERFACE
522            
523             =head2 EXPORTS
524            
525             The following routines are exported by default. They are intended to
526             be called from the command-line, though they could be used from a
527             script.
528            
529             =over
530            
531             =cut
532            
533             =item test( [ %config, ] [ $dist [, $dist .... ] ] )
534            
535             perl -MCPAN::YACSmoke -e test
536            
537             perl -MCPAN::YACSmoke -e test('R/RR/RRWO/Some-Dist-0.01.tar.gz')
538            
539             Runs tests on CPAN distributions. Arguments should be paths of
540             individual distributions in the author directories. If no arguments
541             are given, it will download the F file from CPAN and use that.
542            
543             By default it uses CPANPLUS configuration settings. If CPANPLUS is set
544             not to send test reports, then it will not send test reports.
545            
546             For further use of configuration settings see the new() constructor.
547            
548             =cut
549            
550             sub test {
551 0     0 1 0 my $smoker;
552 0         0 eval {
553 0 0 0     0 if ((ref $_[0]) && $_[0]->isa(__PACKAGE__)) {
554 0         0 $smoker = shift;
555             }
556             };
557 0 0       0 my %config = ref($_[0]) eq 'HASH' ? %{ shift() } : ();
  0         0  
558 0   0     0 $smoker ||= __PACKAGE__->new(%config);
559            
560 0         0 $smoker->_audit("\n\n".('-'x40)."\n");
561            
562 0         0 my @distros = @_;
563 0 0       0 unless (@distros) {
564 0         0 @distros = $smoker->{plugin}->download_list(1);
565 0 0       0 unless (@distros) {
566 0         0 exit error("No new distributions uploaded to be tested");
567             }
568             }
569            
570 0         0 my %paths = $smoker->_build_path_list(
571             $smoker->_remove_excluded_dists( @distros )
572             );
573            
574             # only test as many distributions as specified
575 0         0 my @testlist;
576 0         0 push @testlist, keys %paths;
577            
578 0         0 foreach my $distpath (sort @testlist) {
579 0 0       0 last unless($smoker->{test_max} > 0);
580            
581 0         0 my @versions = @{ $paths{$distpath} };
  0         0  
582 0         0 my @dirs = split /\/+/, $distpath;
583 0         0 my $dist = $dirs[-1];
584            
585             # When there are multiple recent versions of a distribution, we
586             # only want to test the latest one. If it fails, then we'll
587             # check previous distributions.
588            
589 0         0 my $passed = 0;
590 0         0 my $fail_count = 0;
591            
592             # TODO - if test fails due to bad prereqs, set $fail_count to
593             # fail_max and abort testing versions (based on an option)
594            
595 0   0     0 while ( (!$passed) && ($fail_count < $smoker->{fail_max}) &&
      0        
596             (my $ver = shift @versions) ) {
597 0         0 my $distpathver = join("-", $distpath, $ver);
598 0         0 my $distver = join("-", $dist, $ver);
599            
600 0   0     0 my $grade = $smoker->{checked}->{$distver}
601             || 'ungraded';
602            
603 0 0 0     0 if ((!defined $grade) ||
604             $grade =~ /(unknown|ungraded|none)/) {
605            
606 0 0       0 my $mod = $smoker->{cpan}->parse_module( module => $distpathver)
607             or error("Invalid distribution $distver\n");
608            
609 0 0 0     0 if ($mod && (!$mod->is_bundle)) {
610 0         0 $smoker->_audit("\n".('-'x40)."\n");
611 0         0 $smoker->_track("Testing $distpathver");
612 0         0 $smoker->{test_max}--;
613            
614 0         0 eval {
615            
616 0         0 CPANPLUS::Error->flush();
617            
618             # TODO: option to not re-test prereqs that are known to
619             # pass (maybe if we use DBD::SQLite for the database and
620             # mark the date of the result?)
621            
622 0         0 my $stat = $smoker->{cpan}->install(
623             modules => [ $mod ],
624             target => 'create',
625             allow_build_interactively => 0,
626             # other settings not set via set_confi() method
627             );
628            
629             # TODO: check the $stat and react appropriately
630            
631 0         0 $smoker->_audit(CPANPLUS::Error->stack_as_string());
632            
633             # TODO: option to mark uncompleted tests as aborted vs ungraded
634            
635 0   0     0 $grade = ($smoker->{checked}->{$distver} ||= 'aborted');
636 0         0 $passed = ($grade eq 'pass');
637            
638 0         0 $smoker->_audit("\nReport Grade for $distver is ".uc($smoker->{checked}->{$distver})."\n");
639            
640             }; # end eval block
641             }
642             } else {
643 0         0 $passed = ($grade eq 'pass');
644 0         0 $smoker->_audit("$distpathver already tested and graded ".uc($grade)."\n");
645             }
646 0 0       0 $fail_count++, unless ($passed);
647             }
648             }
649 0         0 $smoker = undef;
650            
651             # TODO: repository fills up. An option to flush it is needed.
652            
653             }
654            
655             =item mark( [ %config, ] $dist [, $grade ] ] )
656            
657             perl -MCPAN::YACSmoke -e mark('Some-Dist-0.01')
658            
659             perl -MCPAN::YACSmoke -e mark('Some-Dist-0.01', 'fail')
660            
661             Retrieves the test result in the database, or changes the test result.
662            
663             It can be useful to update the status of a distribution that once
664             failed or was untestable but now works, so as to test modules which
665             make use of it.
666            
667             Grades can be one of (case insensitive):
668            
669             aborted
670             pass
671             fail
672             unknown
673             na
674             ungraded
675             none
676            
677             For further use of configuration settings see the new() constructor.
678            
679             =cut
680            
681             sub mark {
682 0     0 1 0 my $smoker;
683 0         0 eval {
684 0 0 0     0 if ((ref $_[0]) && $_[0]->isa(__PACKAGE__)) {
685 0         0 $smoker = shift;
686             }
687             };
688            
689 0 0       0 my %config = ref($_[0]) eq 'HASH' ? %{ shift() } : ( verbose => 1, );
  0         0  
690 0   0     0 $smoker ||= __PACKAGE__->new( );
691            
692 0         0 $smoker->_audit("\n\n".('-'x40)."\n");
693            
694 0   0     0 my $distver = shift || "";
695 0   0     0 my $grade = lc shift || "";
696            
697 0 0       0 if ($grade) {
698 0 0       0 unless ($grade =~ /(pass|fail|unknown|na|none|ungraded|aborted)/) {
699 0         0 return error("Invalid grade: '$grade'");
700             }
701 0 0       0 if ($grade eq "none") {
702 0         0 $grade = undef;
703             }
704 0         0 $smoker->{checked}->{$distver} = $grade;
705 0   0     0 $smoker->_track("result for '$distver' marked as '" . ($grade||"none")."'");
706             } else {
707 0 0       0 my @distros = ($distver ? ($distver) : $smoker->{plugin}->download_list());
708 0         0 my %paths = $smoker->_build_path_list(
709             $smoker->_remove_excluded_dists( @distros )
710             );
711 0         0 foreach my $dist (sort { versioncmp($a, $b) } keys %paths) {
  0         0  
712 0         0 foreach my $ver (@{ $paths{$dist} }) {
  0         0  
713 0         0 $grade = $smoker->{checked}->{"$dist-$ver"};
714 0 0       0 if ($grade) {
715 0         0 $smoker->_track("result for '$dist-$ver' is '$grade'");
716             } else {
717 0         0 $smoker->_track("no result for '$dist-$ver'");
718             }
719             }
720             }
721             }
722 0         0 $smoker = undef;
723 0 0       0 return $grade if($distver);
724             }
725            
726             =item excluded( [ %config, ] [ $dist [, $dist ... ] ] )
727            
728             perl -MCPAN::YACSmoke -e excluded('Some-Dist-0.01')
729            
730             perl -MCPAN::YACSmoke -e excluded()
731            
732             Given a list of distributions, indicates which ones would be excluded from
733             testing, based on the exclude_dist list that is created.
734            
735             For further use of configuration settings see the new() constructor.
736            
737             =cut
738            
739             sub excluded {
740 0     0 1 0 my $smoker;
741 0         0 eval {
742 0 0 0     0 if ((ref $_[0]) && $_[0]->isa(__PACKAGE__)) {
743 0         0 $smoker = shift;
744             }
745             };
746 0 0       0 my %config = ref($_[0]) eq 'HASH' ? %{ shift() } : ();
  0         0  
747 0   0     0 $smoker ||= __PACKAGE__->new(%config);
748            
749 0         0 $smoker->_audit("\n\n".('-'x40)."\n");
750            
751 0         0 my @distros = @_;
752 0 0       0 unless (@distros) {
753 0         0 @distros = $smoker->{plugin}->download_list();
754 0 0       0 unless (@distros) {
755 0         0 exit err("No new distributions uploaded to be tested");
756             }
757             }
758            
759 0         0 my @dists = $smoker->_remove_excluded_dists( @distros );
760 0         0 $smoker->_audit('EXCLUDED: '.(scalar(@distros) - scalar(@dists))." distributions\n\n");
761 0         0 $smoker = undef;
762 0         0 return @dists;
763             }
764            
765             # TODO: a method to purge older versions of test results from Checked
766             # database. (That is, if the latest version tested is 1.23, we don't
767             # need to keep earlier results around.) There should be an option to
768             # disable this behaviour.
769            
770             ## Private Methods
771            
772             sub _track {
773 0     0   0 my ($self,$message) = @_;
774 0         0 msg($message, $self->{verbose});
775 0         0 $self->_audit($message);
776             }
777            
778             sub _debug {
779 0     0   0 my ($self,$message) = @_;
780 0 0       0 return unless($self->{debug});
781 0         0 $self->_audit($message);
782             }
783            
784             sub _audit {
785 1     1   2 my $self = shift;
786 1 50       17 $self->{audit_cb}->(@_) if($self->{audit_cb});
787 1 50       7 return unless($self->{audit_log});
788            
789 0 0         my $FH = IO::File->new(">>".$self->{audit_log})
790             or exit error("Failed to write to file [$self->{audit_log}]: $!\n");
791 0           print $FH join("\n",@_) . "\n";
792 0           $FH->close;
793             }
794            
795             1;
796             __END__