File Coverage

lib/CPANPLUS/YACSmoke.pm
Criterion Covered Total %
statement 203 326 62.2
branch 46 122 37.7
condition 19 61 31.1
subroutine 29 34 85.2
pod 7 7 100.0
total 304 550 55.2


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