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.08';
3             # Dist::Zilla: +PodWeaver
4             #ABSTRACT: Yet Another CPANPLUS Smoke Tester
5              
6 12     12   9389 use strict;
  12         34  
  12         419  
7 12     12   67 use warnings;
  12         24  
  12         441  
8              
9 12     12   75 use Carp;
  12         25  
  12         814  
10 12     12   100 use CPANPLUS::Backend;
  12         25  
  12         584  
11 12     12   79 use CPANPLUS::Configure;
  12         26  
  12         351  
12 12     12   66 use CPANPLUS::Error;
  12         24  
  12         896  
13 12     12   83 use CPANPLUS::Internals::Constants;
  12         24  
  12         5018  
14 12     12   91 use POSIX qw( O_CREAT O_RDWR O_RDONLY ); # for SDBM_File
  12         26  
  12         153  
15 12     12   13236 use SDBM_File;
  12         5872  
  12         501  
16 12     12   90 use File::Fetch;
  12         27  
  12         307  
17 12     12   61 use IO::File;
  12         25  
  12         2086  
18 12     12   93 use File::Spec::Unix;
  12         25  
  12         553  
19 12     12   5855 use File::Spec::Functions;
  12         9995  
  12         853  
20 12     12   83 use File::Path;
  12         24  
  12         605  
21 12     12   5059 use CPANPLUS::YACSmoke::ReAssemble;
  12         43  
  12         476  
22 12     12   10999 use CPANPLUS::YACSmoke::SortVers;
  12         40  
  12         1271  
23 12     12   4883 use CPANPLUS::YACSmoke::IniFiles;
  12         40  
  12         545  
24              
25 12     12   79 use constant DATABASE_FILE => 'cpansmoke.dat';
  12         26  
  12         1017  
26 12     12   79 use constant CONFIG_FILE => 'cpansmoke.ini';
  12         24  
  12         548  
27 12     12   74 use constant RECENT_FILE => 'RECENT';
  12         23  
  12         41020  
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 23     23   64 my $self = shift;
47 23 50       132 return if $TiedObj;
48 23         225 my $filename = catfile( $self->{conf}->get_conf('base'), DATABASE_FILE );
49 23         8546 $TiedObj = tie %Checked, 'SDBM_File', $filename, O_CREAT|O_RDWR, 0644;
50 23         134 $self->{checked} = \%Checked;
51             }
52              
53             sub _disconnect_db {
54 23     23   92 my $self = shift;
55 23 50       299 return unless $TiedObj;
56 23         76 $TiedObj = undef;
57 23         73 $self->{checked} = undef;
58 23         643 untie %Checked;
59             }
60              
61             sub new {
62 12     12 1 104261 my $package = shift;
63 12 100 66     188 my $nconf = shift if ref $_[0] and $_[0]->isa('CPANPLUS::Configure');
64              
65 12         94 $ENV{AUTOMATED_TESTING} = 1;
66 12         56 $ENV{NONINTERACTIVE_TESTING} = 1; # Lancaster Consensus
67 12         57 $ENV{PERL_MM_USE_DEFAULT} = 1; # despite verbose setting
68 12         50 $ENV{PERL_EXTUTILS_AUTOINSTALL} = '--defaultdeps';
69              
70 12   66     95 my $conf = $nconf || CPANPLUS::Configure->new();
71              
72             # Override configure settings
73 12         10852 $conf->set_conf( prereqs => 2 ); # force to ask callback
74 12         3269 $conf->set_conf( skiptest => 0 );
75 12 50       3022 $conf->set_conf( no_update => 1 )
76             if glob( catfile( $conf->get_conf('base'), $conf->_get_source('stored') .'*'. STORABLE_EXT, ) );
77 12         7108 $conf->set_conf( dist_type => 'CPANPLUS::Dist::YACSmoke' ); # this is where the magic happens.
78 12         3203 $conf->set_conf( cpantest => 'dont_cc' ); # Yes, we want to report test results. But not CC
79 12         3001 $conf->set_conf( verbose => 1 ); # set verbosity to true.
80 12         2835 $conf->set_conf( allow_unknown_prereqs => 0 ); # don't allow unknown prereqs
81              
82 12 50       2852 unless ( defined $ENV{MAILDOMAIN} ) {
83 12   50     122 my $hostpart = ( split /\@/, ( $conf->get_conf( 'email' ) || 'smoker@cpantesters.org' ) )[1];
84 12 50       2954 $ENV{MAILDOMAIN} = $hostpart =~ /^(cpan\.org|gmail\.com)$/i ? 'cpantesters.org' : $hostpart;
85             }
86              
87 12 50       603 if ( $^V gt v5.9.5 ) {
88 12         97 $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 12         3056 my $cb = CPANPLUS::Backend->new($conf);
96              
97 12         347255 my $exclude_dists;
98             my $exclude_auths;
99 12         0 my $local_lib;
100 12         234 my $config_file = catfile( $conf->get_conf('base'), CONFIG_FILE );
101 12 100       4459 if ( -r $config_file ) {
102 3         338 my $cfg = CPANPLUS::YACSmoke::IniFiles->new(-file => $config_file);
103             {
104 3         20 my @list = $cfg->val( 'CONFIG', 'exclude_dists' );
105 3 100       11 if ( @list ) {
106 1         23 $exclude_dists = CPANPLUS::YACSmoke::ReAssemble->new();
107 1         14 $exclude_dists->add( @list );
108             }
109             }
110             {
111 3         8 my @list = $cfg->val( 'CONFIG', 'exclude_auths' );
  3         6  
  3         16  
112 3 100       15 if ( @list ) {
113 2         40 $exclude_auths = CPANPLUS::YACSmoke::ReAssemble->new();
114 2         20 $exclude_auths->add( @list );
115             }
116             }
117 3         20 $local_lib = $cfg->val( 'CONFIG', 'local::lib' );
118             }
119              
120 12         777 my $self = bless { @_ }, $package;
121 12         195 $self->{conf} = $conf;
122 12         106 $self->{cpanplus} = $cb;
123 12         247 $self->{exclude_dists} = $exclude_dists;
124 12         57 $self->{exclude_auths} = $exclude_auths;
125 12         57 $self->{allow_retries} = 'aborted|ungraded';
126 12         44 $self->{local_lib} = $local_lib;
127 12         169 return $self;
128             }
129              
130             sub test {
131 7     7 1 35396 my $self;
132 7         21 eval {
133 7 50 33     150 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
134 7         35 $self = shift;
135             }
136             };
137 7   33     40 $self ||= __PACKAGE__->new();
138 7         58 $self->_connect_db();
139              
140 7         45 my @dists = @_;
141              
142 7 50       44 unless ( @dists ) {
143 0         0 @dists = $self->_download_list();
144             }
145              
146 7         30 my @mods;
147              
148 7         57 foreach my $dist ( @dists ) {
149 7         102 my $mod = $self->{cpanplus}->parse_module( module => $dist );
150 7 50       1089878 next unless $mod;
151 7         547 my $package = $mod->package_name .'-'. $mod->package_version;
152 7   50     3297 my $grade = $self->{checked}->{$package} || 'ungraded';
153 7 50       163 next if $self->_is_excluded_dist($package);
154 7 50       152 next if $mod->is_bundle;
155 7 50       2909 next unless $grade =~ /$self->{allow_retries}/;
156 7         82 push @mods, $mod;
157             }
158              
159 7         92 $self->_disconnect_db();
160              
161 7         58 my $target = 'create';
162              
163 7 50       65 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 7         67 foreach my $mod ( @mods ) {
173 7         38 eval {
174 7         107 CPANPLUS::Error->flush();
175             my $stat = $self->{cpanplus}->install(
176 7         985 modules => [ $mod ],
177             target => $target,
178             allow_build_interactively => 0,
179             # other settings now set via set_config() method
180             );
181             };
182             }
183              
184 7 50       368375 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 7 50       129 $self->{cpanplus}->save_state() if !$self->{local_lib};
192              
193 7         32346 return 1;
194             }
195              
196             sub mark {
197 16     16 1 56898 my $self;
198 16         48 eval {
199 16 50 33     277 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
200 16         54 $self = shift;
201             }
202             };
203 16   33     55 $self ||= __PACKAGE__->new();
204 16         75 $self->_connect_db();
205              
206 16   50     105 my $distver = shift || '';
207 16   100     555 my $grade = lc shift || '';
208              
209 16 100       97 if ($grade) {
210 4         21 my $mod = $self->{cpanplus}->parse_module( module => $distver );
211 4 50       593 return error(qq{Invalid distribution "$distver"}) unless $mod;
212              
213 4 50       45 unless ($grade =~ /(pass|fail|unknown|na|none|ungraded|aborted|ignored)/) {
214 0         0 return error("Invalid grade: '$grade'");
215             }
216 4 50       13 if ($grade eq "none") {
217 0         0 $grade = undef;
218             }
219              
220 4         13 $distver = $mod->package_name .'-'. $mod->package_version;
221 4         1623 $self->{checked}->{$distver} = $grade;
222             }
223             else {
224 12 50       78 my @distros = ($distver ? ($distver) : $self->_download_list());
225 12         66 foreach my $dist ( @distros ) {
226 12         126 my $mod = $self->{cpanplus}->parse_module( module => $dist );
227 12 50       184812 next unless $mod;
228 12         155 my $dist_ver = $mod->package_name .'-'. $mod->package_version;
229 12 50       4679 next if $self->_is_excluded_dist( $dist_ver );
230 12         645 $grade = $self->{checked}->{$dist_ver};
231 12 100       87 if ( $grade ) {
232 9         67 msg(qq{result for "$dist_ver" is "$grade"});
233             }
234             else {
235 3         42 msg(qq{no result for "$dist_ver"});
236             }
237             }
238             }
239 16         13994 $self->_disconnect_db();
240 16 50       143 return $grade if $distver;
241             }
242              
243             sub excluded {
244 2     2 1 8117 my $self;
245 2         5 eval {
246 2 50 33     31 if ( (ref $_[0]) && $_[0]->isa(__PACKAGE__) ) {
247 2         7 $self = shift;
248             }
249             };
250 2   33     6 $self ||= __PACKAGE__->new();
251              
252 2         11 my @dists = @_;
253              
254 2 50       7 unless ( @dists ) {
255 0         0 @dists = $self->_download_list();
256             }
257              
258 2         5 my @mods;
259              
260 2         7 foreach my $dist ( @dists ) {
261 2         18 my $mod = $self->{cpanplus}->parse_module( module => $dist );
262 2 50       144785 next unless $mod;
263 2         23 my $package = $mod->package_name .'-'. $mod->package_version;
264 2         997 my $auth = $mod->author->cpanid;
265 2 50 66     58 next unless $self->_is_excluded_dist($package) || $self->_is_excluded_auth($auth);
266 2         15 msg(qq{EXCLUDED: "$package"});
267 2         2130 push @mods, $package;
268             }
269              
270 2         33 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 21     21   123 my $self = shift;
392 21   50     124 my $dist = shift || return;
393 21 100       181 return unless $self->{exclude_dists};
394 2 100       9 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     4 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   2722 my $self = shift;
461 1 50       5 return if !$self->{local_lib};
462 1         13 require Cwd;
463 1         5 require File::Temp;
464 1         589 require CPANPLUS::YACSmoke::locallib;
465 1         18 my $tmpdir = File::Temp::tempdir( DIR => '.', CLEANUP => 1 );
466 1         593 my $abs = Cwd::abs_path($tmpdir);
467 1         12 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__