File Coverage

lib/CPANPLUS/YACSmoke.pm
Criterion Covered Total %
statement 203 324 62.6
branch 46 122 37.7
condition 19 61 31.1
subroutine 29 34 85.2
pod 7 7 100.0
total 304 548 55.4


line stmt bran cond sub pod time code
1             package CPANPLUS::YACSmoke;
2             $CPANPLUS::YACSmoke::VERSION = '1.04';
3             # Dist::Zilla: +PodWeaver
4             #ABSTRACT: Yet Another CPANPLUS Smoke Tester
5              
6 10     10   7306 use strict;
  10         23  
  10         356  
7 10     10   58 use warnings;
  10         23  
  10         334  
8              
9 10     10   58 use Carp;
  10         20  
  10         652  
10 10     10   68 use CPANPLUS::Backend;
  10         20  
  10         497  
11 10     10   61 use CPANPLUS::Configure;
  10         18  
  10         240  
12 10     10   57 use CPANPLUS::Error;
  10         17  
  10         661  
13 10     10   67 use CPANPLUS::Internals::Constants;
  10         16  
  10         3774  
14 10     10   71 use POSIX qw( O_CREAT O_RDWR O_RDONLY ); # for SDBM_File
  10         19  
  10         115  
15 10     10   9738 use SDBM_File;
  10         4656  
  10         395  
16 10     10   65 use File::Fetch;
  10         20  
  10         252  
17 10     10   49 use IO::File;
  10         22  
  10         1727  
18 10     10   68 use File::Spec::Unix;
  10         34  
  10         446  
19 10     10   4644 use File::Spec::Functions;
  10         7841  
  10         673  
20 10     10   66 use File::Path;
  10         20  
  10         457  
21 10     10   3969 use CPANPLUS::YACSmoke::ReAssemble;
  10         33  
  10         368  
22 10     10   8940 use CPANPLUS::YACSmoke::SortVers;
  10         28  
  10         1020  
23 10     10   3734 use CPANPLUS::YACSmoke::IniFiles;
  10         33  
  10         412  
24              
25 10     10   64 use constant DATABASE_FILE => 'cpansmoke.dat';
  10         20  
  10         703  
26 10     10   58 use constant CONFIG_FILE => 'cpansmoke.ini';
  10         20  
  10         445  
27 10     10   56 use constant RECENT_FILE => 'RECENT';
  10         20  
  10         32023  
28              
29             require Exporter;
30              
31             our @ISA = qw( Exporter );
32             our %EXPORT_TAGS = (
33             'all' => [ qw( mark test excluded purge flush reindex) ],
34             'default' => [ qw( mark test excluded ) ],
35             );
36              
37             our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
38             our @EXPORT = ( @{ $EXPORT_TAGS{'default'} } );
39              
40             {
41             my %Checked;
42             my $TiedObj;
43              
44              
45             sub _connect_db {
46 19     19   38 my $self = shift;
47 19 50       74 return if $TiedObj;
48 19         169 my $filename = catfile( $self->{conf}->get_conf('base'), DATABASE_FILE );
49 19         5989 $TiedObj = tie %Checked, 'SDBM_File', $filename, O_CREAT|O_RDWR, 0644;
50 19         97 $self->{checked} = \%Checked;
51             }
52              
53             sub _disconnect_db {
54 19     19   45 my $self = shift;
55 19 50       192 return unless $TiedObj;
56 19         48 $TiedObj = undef;
57 19         61 $self->{checked} = undef;
58 19         421 untie %Checked;
59             }
60              
61             sub new {
62 10     10 1 78784 my $package = shift;
63 10 100 66     134 my $nconf = shift if ref $_[0] and $_[0]->isa('CPANPLUS::Configure');
64              
65 10         74 $ENV{AUTOMATED_TESTING} = 1;
66 10         47 $ENV{NONINTERACTIVE_TESTING} = 1; # Lancaster Consensus
67 10         56 $ENV{PERL_MM_USE_DEFAULT} = 1; # despite verbose setting
68 10         49 $ENV{PERL_EXTUTILS_AUTOINSTALL} = '--defaultdeps';
69              
70 10   66     64 my $conf = $nconf || CPANPLUS::Configure->new();
71              
72             # Override configure settings
73 10         10528 $conf->set_conf( prereqs => 2 ); # force to ask callback
74 10         2554 $conf->set_conf( skiptest => 0 );
75 10 50       2502 $conf->set_conf( no_update => 1 )
76             if glob( catfile( $conf->get_conf('base'), $conf->_get_source('stored') .'*'. STORABLE_EXT, ) );
77 10         5568 $conf->set_conf( dist_type => 'CPANPLUS::Dist::YACSmoke' ); # this is where the magic happens.
78 10         2596 $conf->set_conf( cpantest => 'dont_cc' ); # Yes, we want to report test results. But not CC
79 10         2455 $conf->set_conf( verbose => 1 ); # set verbosity to true.
80 10         2359 $conf->set_conf( allow_unknown_prereqs => 0 ); # don't allow unknown prereqs
81              
82 10 50       2311 unless ( defined $ENV{MAILDOMAIN} ) {
83 10   50     106 my $hostpart = ( split /\@/, ( $conf->get_conf( 'email' ) || 'smoker@cpantesters.org' ) )[1];
84 10 50       2359 $ENV{MAILDOMAIN} = $hostpart =~ /^(cpan\.org|gmail\.com)$/i ? 'cpantesters.org' : $hostpart;
85             }
86              
87 10 50       470 if ( $^V gt v5.9.5 ) {
88 10         74 $conf->set_conf( prefer_makefile => 0 ); # Prefer Build.PL if we have M::B
89             }
90             else {
91 0         0 eval "require Module::Build";
92 0 0       0 $conf->set_conf( prefer_makefile => 0 ) unless $@; #
93             }
94              
95 10         2471 my $cb = CPANPLUS::Backend->new($conf);
96              
97 10         273405 my $exclude_dists;
98             my $exclude_auths;
99 10         0 my $local_lib;
100 10         207 my $config_file = catfile( $conf->get_conf('base'), CONFIG_FILE );
101 10 100       3596 if ( -r $config_file ) {
102 3         312 my $cfg = CPANPLUS::YACSmoke::IniFiles->new(-file => $config_file);
103             {
104 3         18 my @list = $cfg->val( 'CONFIG', 'exclude_dists' );
105 3 100       10 if ( @list ) {
106 1         19 $exclude_dists = CPANPLUS::YACSmoke::ReAssemble->new();
107 1         8 $exclude_dists->add( @list );
108             }
109             }
110             {
111 3         9 my @list = $cfg->val( 'CONFIG', 'exclude_auths' );
  3         6  
  3         15  
112 3 100       13 if ( @list ) {
113 2         33 $exclude_auths = CPANPLUS::YACSmoke::ReAssemble->new();
114 2         13 $exclude_auths->add( @list );
115             }
116             }
117 3         18 $local_lib = $cfg->val( 'CONFIG', 'local::lib' );
118             }
119              
120 10         488 my $self = bless { @_ }, $package;
121 10         157 $self->{conf} = $conf;
122 10         79 $self->{cpanplus} = $cb;
123 10         163 $self->{exclude_dists} = $exclude_dists;
124 10         41 $self->{exclude_auths} = $exclude_auths;
125 10         48 $self->{allow_retries} = 'aborted|ungraded';
126 10         34 $self->{local_lib} = $local_lib;
127 10         139 return $self;
128             }
129              
130             sub test {
131 5     5 1 25767 my $self;
132 5         14 eval {
133 5 50 33     99 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
134 5         26 $self = shift;
135             }
136             };
137 5   33     26 $self ||= __PACKAGE__->new();
138 5         35 $self->_connect_db();
139              
140 5         31 my @dists = @_;
141              
142 5 50       29 unless ( @dists ) {
143 0         0 @dists = $self->_download_list();
144             }
145              
146 5         13 my @mods;
147              
148 5         35 foreach my $dist ( @dists ) {
149 5         73 my $mod = $self->{cpanplus}->parse_module( module => $dist );
150 5 50       753526 next unless $mod;
151 5         126 my $package = $mod->package_name .'-'. $mod->package_version;
152 5   50     2172 my $grade = $self->{checked}->{$package} || 'ungraded';
153 5 50       98 next if $self->_is_excluded_dist($package);
154 5 50       92 next if $mod->is_bundle;
155 5 50       1947 next unless $grade =~ /$self->{allow_retries}/;
156 5         61 push @mods, $mod;
157             }
158              
159 5         65 $self->_disconnect_db();
160              
161 5         40 my $target = 'create';
162              
163 5 50       48 if ( $self->{local_lib} ) {
164 0         0 $self->_setup_local_lib();
165 0         0 $self->{conf}->_perl5lib( $ENV{PERL5LIB} );
166 0         0 $target = 'install';
167 0         0 msg("Setup local::lib environment in '$ENV{PERL_LOCAL_LIB_ROOT}'");
168             }
169              
170 5         23 foreach my $mod ( @mods ) {
171 5         16 eval {
172 5         57 CPANPLUS::Error->flush();
173             my $stat = $self->{cpanplus}->install(
174 5         643 modules => [ $mod ],
175             target => $target,
176             allow_build_interactively => 0,
177             # other settings now set via set_config() method
178             );
179             };
180             }
181              
182 5 50       30213 if ( $self->{local_lib} ) {
183 0         0 my $build_dir = $self->_get_build_dir();
184 0         0 msg("Flushing '$build_dir'");
185 0 0       0 rmtree($build_dir) if -e $build_dir;
186 0         0 msg("Flushed '$build_dir'");
187             }
188              
189 5 50       69 $self->{cpanplus}->save_state() if !$self->{local_lib};
190              
191 5         21564 return 1;
192             }
193              
194             sub mark {
195 14     14 1 43783 my $self;
196 14         33 eval {
197 14 50 33     292 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
198 14         39 $self = shift;
199             }
200             };
201 14   33     39 $self ||= __PACKAGE__->new();
202 14         61 $self->_connect_db();
203              
204 14   50     65 my $distver = shift || '';
205 14   100     401 my $grade = lc shift || '';
206              
207 14 100       89 if ($grade) {
208 4         26 my $mod = $self->{cpanplus}->parse_module( module => $distver );
209 4 50       511 return error(qq{Invalid distribution "$distver"}) unless $mod;
210              
211 4 50       30 unless ($grade =~ /(pass|fail|unknown|na|none|ungraded|aborted|ignored)/) {
212 0         0 return error("Invalid grade: '$grade'");
213             }
214 4 50       13 if ($grade eq "none") {
215 0         0 $grade = undef;
216             }
217              
218 4         12 $distver = $mod->package_name .'-'. $mod->package_version;
219 4         1297 $self->{checked}->{$distver} = $grade;
220             }
221             else {
222 10 50       51 my @distros = ($distver ? ($distver) : $self->_download_list());
223 10         62 foreach my $dist ( @distros ) {
224 10         97 my $mod = $self->{cpanplus}->parse_module( module => $dist );
225 10 50       146429 next unless $mod;
226 10         65 my $dist_ver = $mod->package_name .'-'. $mod->package_version;
227 10 50       3466 next if $self->_is_excluded_dist( $dist_ver );
228 10         207 $grade = $self->{checked}->{$dist_ver};
229 10 100       145 if ( $grade ) {
230 8         57 msg(qq{result for "$dist_ver" is "$grade"});
231             }
232             else {
233 2         25 msg(qq{no result for "$dist_ver"});
234             }
235             }
236             }
237 14         10343 $self->_disconnect_db();
238 14 50       109 return $grade if $distver;
239             }
240              
241             sub excluded {
242 2     2 1 5904 my $self;
243 2         4 eval {
244 2 50 33     26 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
245 2         7 $self = shift;
246             }
247             };
248 2   33     11 $self ||= __PACKAGE__->new();
249              
250 2         9 my @dists = @_;
251              
252 2 50       6 unless ( @dists ) {
253 0         0 @dists = $self->_download_list();
254             }
255              
256 2         3 my @mods;
257              
258 2         8 foreach my $dist ( @dists ) {
259 2         14 my $mod = $self->{cpanplus}->parse_module( module => $dist );
260 2 50       124007 next unless $mod;
261 2         21 my $package = $mod->package_name .'-'. $mod->package_version;
262 2         778 my $auth = $mod->author->cpanid;
263 2 50 66     49 next unless $self->_is_excluded_dist($package) || $self->_is_excluded_auth($auth);
264 2         12 msg(qq{EXCLUDED: "$package"});
265 2         1776 push @mods, $package;
266             }
267              
268 2         27 return @mods;
269             }
270              
271             sub purge {
272 0     0 1 0 my $self;
273 0         0 eval {
274 0 0 0     0 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
275 0         0 $self = shift;
276             }
277             };
278 0   0     0 $self ||= __PACKAGE__->new();
279 0 0       0 my %config = ref($_[0]) eq 'HASH' ? %{ shift() } : ();
  0         0  
280 0         0 $self->_connect_db();
281              
282 0   0     0 my $flush = $config{flush_flag} || 0;
283 0         0 my %distvars;
284 0         0 my $override = 0;
285              
286 0 0       0 if(@_) {
287 0         0 $override = 1;
288 0         0 for(@_) {
289 0 0       0 next unless(/^(.*)\-(.+)$/);
290 0         0 push @{$distvars{$1}}, $2;
  0         0  
291             }
292             }
293             else {
294 0         0 for(keys %{$self->{checked}}) {
  0         0  
295 0 0       0 next unless(/^(.*)\-(.+)$/);
296 0         0 push @{$distvars{$1}}, $2;
  0         0  
297             }
298             }
299              
300 0         0 foreach my $dist (sort keys %distvars) {
301 0         0 my $passed = $override;
302 0         0 my @vers = sort { versioncmp($a, $b) } @{$distvars{$dist}};
  0         0  
  0         0  
303 0         0 while(@vers) {
304 0         0 my $vers = pop @vers; # the latest
305 0 0       0 if($passed) {
    0          
306             msg("'$dist-$vers' ['".
307 0         0 uc($self->{checked}->{"$dist-$vers"}).
308             "'] has been purged");
309 0         0 delete $self->{checked}->{"$dist-$vers"};
310 0 0       0 if($flush) {
311 0         0 my $builddir =
312             catfile($self->_get_build_dir(), "$dist-$vers");
313 0 0       0 rmtree($builddir) if(-d $builddir);
314             }
315             }
316             elsif($self->{checked}->{"$dist-$vers"} eq 'pass') {
317 0         0 $passed = 1;
318             }
319             }
320             }
321              
322 0         0 $self->_disconnect_db();
323 0         0 return 1;
324             }
325              
326             sub flush {
327 0     0 1 0 my $self;
328 0         0 eval {
329 0 0 0     0 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
330 0         0 $self = shift;
331             }
332             };
333 0   0     0 $self ||= __PACKAGE__->new();
334 0 0       0 my %config = ref($_[0]) eq 'HASH' ? %{ shift() } : ();
  0         0  
335 0         0 $self->_connect_db();
336              
337 0   0     0 my $param = shift || 'all';
338              
339 0         0 my $build_dir = $self->_get_build_dir();
340              
341 0 0       0 if ( $param eq 'old' ) {
342 0         0 my %dists;
343 0         0 opendir(my $DIR, $build_dir);
344 0         0 while(my $dir = readdir($DIR)) {
345 0 0       0 next if $dir =~ /^\.+$/;
346 0         0 $dir =~ /(.*)-(.+)/;
347 0         0 $dists{$1}->{$2} = "$dir";
348             }
349 0         0 closedir($DIR);
350 0         0 for my $dist (keys %dists) {
351 0         0 for(sort { versioncmp($a, $b) } keys %{$dists{$dist}}) {
  0         0  
  0         0  
352 0         0 rmtree(catfile($build_dir,$dists{$dist}->{$_}));
353 0         0 msg("'$dists{$dist}->{$_}' flushed");
354             }
355             }
356             }
357             else {
358 0         0 msg("Flushing '$build_dir'");
359 0 0       0 rmtree($build_dir) if -e $build_dir;
360 0         0 msg("Flushed '$build_dir'");
361 0         0 require File::Glob;
362 0         0 ( my $base = $self->{conf}->get_conf('base') ) =~ s![\\/]$!!;
363 0         0 foreach my $sourcefile ( File::Glob::bsd_glob( $base . q{/sourcefile*} ) ) {
364 0         0 msg("Removing '$sourcefile'");
365 0         0 rmtree $sourcefile;
366             }
367             }
368              
369 0         0 $self->_disconnect_db();
370 0         0 return 1;
371             }
372              
373             sub reindex {
374 0     0 1 0 my $self;
375 0         0 eval {
376 0 0 0     0 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
377 0         0 $self = shift;
378             }
379             };
380 0   0     0 $self ||= __PACKAGE__->new();
381 0         0 $self->{conf}->set_conf( no_update => 0 );
382 0         0 $self->{cpanplus}->reload_indices( update_source => 1 );
383             $self->{conf}->set_conf( no_update => 1 )
384 0 0       0 if glob( catfile( $self->{conf}->get_conf('base'), $self->{conf}->_get_source('stored') .'*'. STORABLE_EXT, ) );
385 0         0 return 1;
386             }
387              
388             sub _is_excluded_dist {
389 17     17   63 my $self = shift;
390 17   50     83 my $dist = shift || return;
391 17 100       145 return unless $self->{exclude_dists};
392 2 100       7 return 1 if $dist =~ $self->{exclude_dists}->re();
393             }
394              
395             sub _is_excluded_auth {
396 1     1   2 my $self = shift;
397 1   50     11 my $auth = shift || return;
398 1 50       6 return unless $self->{exclude_auths};
399 1 50       3 return 1 if $auth =~ $self->{exclude_auths}->re();
400             }
401              
402             sub _download_list {
403 0     0   0 my $self = shift;
404              
405 0         0 my $path = $self->{conf}->get_conf('base');
406 0         0 my $local = catfile( $path, RECENT_FILE );
407              
408 0         0 my $hosts = $self->{conf}->get_conf('hosts');
409 0         0 my $h_ind = 0;
410              
411 0         0 while ($h_ind < @$hosts) {
412 0         0 my $host = $hosts->[$h_ind];
413             my $mirror_path = File::Spec::Unix->catfile(
414 0         0 $host->{'path'},
415             RECENT_FILE
416             );
417              
418             my %args = (
419             scheme => $host->{scheme},
420             host => $host->{host},
421 0         0 path => $mirror_path,
422             );
423              
424 0         0 my $remote = $self->{cpanplus}->_host_to_uri( %args );
425              
426 0         0 my $ff = File::Fetch->new( uri => $remote );
427 0         0 my $status = $ff->fetch( to => $path );
428 0 0       0 last if $status;
429 0         0 $h_ind++;
430             }
431              
432 0 0       0 return () if(@$hosts == $h_ind); # no host accessible
433              
434 0         0 my @testlist;
435 0 0       0 my $fh = IO::File->new($local)
436             or croak("Cannot access local RECENT file [$local]: $!\n");
437 0         0 while (<$fh>) {
438 0 0       0 next unless(/^authors/);
439 0 0       0 next unless(/\.(?:tar\.(?:bz2|gz|Z)|t(?:gz|bz)|(?
440 0         0 s!authors/id/!!;
441 0         0 chomp;
442 0         0 push @testlist, $_;
443             }
444              
445 0         0 return @testlist;
446             }
447              
448             sub _get_build_dir {
449 0     0   0 my $self = shift;
450             File::Spec->catdir(
451             $self->{conf}->get_conf('base'),
452             $self->{cpanplus}->_perl_version( perl => $^X ),
453 0         0 $self->{conf}->_get_build('moddir')
454             );
455             }
456              
457             sub _setup_local_lib {
458 1     1   2719 my $self = shift;
459 1 50       6 return if !$self->{local_lib};
460 1         7 require Cwd;
461 1         4 require File::Temp;
462 1         634 require CPANPLUS::YACSmoke::locallib;
463 1         14 my $tmpdir = File::Temp::tempdir( DIR => '.', CLEANUP => 1 );
464 1         603 my $abs = Cwd::abs_path($tmpdir);
465 1         12 CPANPLUS::YACSmoke::locallib->ensure_dir_structure_for( $abs, { quiet => 1 } );
466 1         6 CPANPLUS::YACSmoke::locallib->setup_env_hash_for( $abs );
467             }
468              
469             }
470              
471             'Yakkity Yac';
472              
473             __END__