File Coverage

blib/lib/Test/DependentModules.pm
Criterion Covered Total %
statement 85 279 30.4
branch 10 78 12.8
condition 1 21 4.7
subroutine 27 53 50.9
pod 3 3 100.0
total 126 434 29.0


line stmt bran cond sub pod time code
1             package Test::DependentModules;
2              
3 1     1   36243 use strict;
  1         2  
  1         24  
4 1     1   6 use warnings;
  1         2  
  1         30  
5 1     1   886 use autodie;
  1         19121  
  1         4  
6              
7             our $VERSION = '0.25';
8              
9             # CPAN::Reporter spits out random output we don't want, and we don't want to
10             # report these tests anyway.
11             BEGIN {
12             ## no critic (Variables::RequireLocalizedPunctuationVars)
13 1     1   6555 $INC{'CPAN/Reporter.pm'} = 0;
14             }
15              
16 1     1   968 use Capture::Tiny qw( capture );
  1         156479  
  1         138  
17 1     1   12 use Cwd qw( abs_path );
  1         76  
  1         85  
18 1     1   10 use Exporter qw( import );
  1         4  
  1         47  
19 1     1   9 use File::Path qw( rmtree );
  1         3  
  1         68  
20 1     1   8 use File::Spec;
  1         3  
  1         49  
21 1     1   8 use File::Temp qw( tempdir );
  1         3  
  1         76  
22 1     1   8 use File::chdir;
  1         2  
  1         128  
23 1     1   1170 use IO::Handle::Util qw( io_from_write_cb );
  1         703755  
  1         8  
24 1     1   345429 use IPC::Run3 qw( run3 );
  1         188051  
  1         64  
25 1     1   892 use Log::Dispatch;
  1         14167  
  1         31  
26 1     1   274613 use MetaCPAN::Client;
  1         2308858  
  1         34  
27 1     1   8 use Test::Builder;
  1         2  
  1         32  
28 1     1   6 use Try::Tiny;
  1         3  
  1         2811  
29              
30             our @EXPORT_OK = qw( test_all_dependents test_module test_modules );
31              
32             ## no critic (Variables::RequireLocalizedPunctuationVars)
33             $ENV{PERL5LIB} = join q{:}, ( $ENV{PERL5LIB} || q{} ),
34             File::Spec->catdir( _temp_lib_dir(), 'lib', 'perl5' );
35             $ENV{PERL_AUTOINSTALL} = '--defaultdeps';
36             $ENV{PERL_MM_USE_DEFAULT} = 1;
37             ## use critic
38              
39             my $Test = Test::Builder->new;
40              
41             sub test_all_dependents {
42 0     0 1 0 my $module = shift;
43 0         0 my $params = shift;
44              
45 0         0 _load_cpan();
46 0         0 _make_logs();
47              
48 0         0 my @deps = _get_deps( $module, $params );
49              
50 0         0 $Test->plan( tests => scalar @deps );
51              
52 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
53 0         0 test_modules(@deps);
54             }
55              
56             sub _get_deps {
57 0     0   0 my $module = shift;
58 0         0 my $params = shift;
59              
60 0         0 $module =~ s/::/-/g;
61              
62 0         0 my $rev_deps = MetaCPAN::Client->new->rev_deps($module);
63              
64             my $allow
65             = $params->{filter} ? $params->{filter}
66 0     0   0 : $params->{exclude} ? sub { $_[0] !~ /$params->{exclude}/ }
67 0 0   0   0 : sub {1};
  0 0       0  
68              
69 0         0 my @deps;
70 0         0 while ( my $dep = $rev_deps->next ) {
71 0         0 my $dist = $dep->distribution;
72              
73 0 0       0 next unless $allow->($dist);
74 0 0       0 next if $dist =~ /^(?:Task|Bundle)/;
75              
76 0         0 push @deps => $dist;
77             }
78              
79             ## no critic (Subroutines::ProhibitReturnSort)
80 0         0 return sort { lc $a cmp lc $b } @deps;
  0         0  
81             }
82              
83             sub test_modules {
84 0     0 1 0 _load_cpan();
85 0         0 _make_logs();
86              
87 0         0 my $parallel = 0;
88 0 0 0     0 if ( $ENV{PERL_TEST_DM_PROCESSES}
89             && $ENV{PERL_TEST_DM_PROCESSES} > 1 ) {
90              
91 0 0       0 if ( eval { require Parallel::ForkManager; 1; } ) {
  0         0  
  0         0  
92 0         0 $parallel = 1;
93             }
94             else {
95 0         0 warn
96             'Cannot run multiple processes without the Parallel::ForkManager module.';
97             }
98             }
99              
100 0 0       0 if ($parallel) {
101 0         0 _test_in_parallel(@_);
102             }
103             else {
104 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
105 0         0 for my $module (@_) {
106 0         0 test_module($module);
107             }
108             }
109             }
110              
111             sub _test_in_parallel {
112 0     0   0 my @modules = @_;
113              
114 0         0 my $pm = Parallel::ForkManager->new( $ENV{PERL_TEST_DM_PROCESSES} );
115              
116             $pm->run_on_finish(
117             sub {
118 0     0   0 shift; # pid
119 0         0 shift; # program exit code
120 0         0 shift; # ident
121 0         0 shift; # exit signal
122 0         0 shift; # core dump
123 0         0 my $results = shift;
124              
125 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
126             _test_report(
127 0         0 @{$results}{qw( name passed summary output stderr skipped )}
  0         0  
128             );
129             }
130 0         0 );
131              
132 0         0 for my $module (@_) {
133 0 0       0 $pm->start and next;
134              
135 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
136 0         0 test_module( $module, $pm );
137             }
138              
139 0         0 $pm->wait_all_children;
140             }
141              
142             sub test_module {
143 0     0 1 0 my $name = shift;
144 0         0 my $pm = shift;
145              
146 0         0 _load_cpan();
147 0         0 _make_logs();
148              
149 0         0 $name =~ s/-/::/g;
150              
151 0         0 my $dist = _get_distro($name);
152 0 0       0 unless ($dist) {
153 0         0 $Test->diag(qq{Could't find a distro for $name});
154 0         0 return;
155             }
156              
157 0         0 $Test->diag( 'Testing ' . $dist->base_id );
158              
159 0 0       0 unless ($dist) {
160 0         0 $name =~ s/::/-/g;
161 0 0       0 my $todo
162             = defined( $Test->todo )
163             ? ' (TODO: ' . $Test->todo . ')'
164             : q{};
165 0         0 my $summary = "FAIL${todo}: $name - ??? - ???";
166 0         0 my $output = "Could not find $name on CPAN\n";
167 0 0       0 if ($pm) {
168 0         0 $pm->finish(
169             0, {
170             name => $name,
171             passed => 0,
172             summary => $summary,
173             output => $output,
174             stderr => $output,
175             }
176             );
177             }
178             else {
179 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
180 0         0 _test_report( $name, 0, $summary, $output, $output, undef );
181             }
182              
183 0         0 return;
184             }
185              
186 0         0 $name = $dist->base_id;
187              
188             my $success = try {
189 0     0   0 capture { _install_prereqs($dist) };
  0         0  
190 0         0 1;
191             }
192             catch {
193 0     0   0 local $Test::Builder::Level = $Test::Builder::Level + 1;
194 0         0 my $msg = "Installing prereqs for $name failed: $_";
195 0         0 $msg =~ s/\s*$//;
196 0         0 $msg =~ s/\n/\t/g;
197 0 0       0 if ($pm) {
198 0         0 $pm->finish(
199             0, {
200             name => $name,
201             skipped => $msg,
202             }
203             );
204             }
205             else {
206 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
207 0         0 _test_report( $name, undef, undef, undef, undef, $msg );
208             }
209              
210 0         0 return;
211 0         0 };
212              
213 0 0       0 return unless $success;
214              
215 0         0 my ( $passed, $output, $stderr ) = _run_tests_for_dir( $dist->dir );
216              
217             # A lot of modules seem to have cargo-culted a diag() that looks like this
218             # ...
219             #
220             # Testing Foo::Bar 0.01, Perl 5.00801, /usr/bin/perl
221 0 0 0     0 $stderr = q{}
222             if defined $stderr && $stderr =~ /\A\# Testing [\w:]+ [^\n]+\Z/;
223              
224 0 0 0     0 my $status = $passed && $stderr ? 'WARN' : $passed ? 'PASS' : 'FAIL';
    0          
225 0 0       0 if ( my $reason = $Test->todo ) {
226 0         0 $status .= " (TODO: $reason)";
227             }
228              
229 0         0 my $summary
230             = "$status: $name - " . $dist->base_id . ' - ' . $dist->author->id;
231              
232 0 0       0 if ($pm) {
233 0         0 $pm->finish(
234             0, {
235             name => $name,
236             passed => $passed,
237             summary => $summary,
238             output => $output,
239             stderr => $stderr,
240             }
241             );
242             }
243             else {
244 0         0 local $Test::Builder::Level = $Test::Builder::Level + 1;
245 0         0 _test_report( $name, $passed, $summary, $output, $stderr );
246             }
247             }
248              
249             ## no critic (Subroutines::ProhibitManyArgs)
250             sub _test_report {
251 0     0   0 my $name = shift;
252 0         0 my $passed = shift;
253 0         0 my $summary = shift;
254 0         0 my $output = shift;
255 0         0 my $stderr = shift;
256 0         0 my $skipped = shift;
257              
258 0 0       0 if ($skipped) {
259 0         0 _status_log("UNKNOWN: $name ($skipped)\n");
260 0         0 _error_log("UNKNOWN: $name ($skipped)\n");
261              
262 0         0 $Test->diag("Skipping $name: $skipped");
263 0         0 $Test->skip($skipped);
264             }
265             else {
266 0         0 _status_log("$summary\n");
267 0         0 _error_log("$summary\n");
268              
269 0         0 $Test->ok( $passed, "$name passed all tests" );
270             }
271              
272 0 0 0     0 if ( $passed || $skipped ) {
273 0         0 _error_log("\n");
274             }
275             else {
276 0         0 _error_log( q{-} x 50 );
277 0         0 _error_log("\n");
278 0 0       0 _error_log("$output\n") if defined $output;
279             }
280             }
281              
282             {
283             my %logs;
284              
285             sub _make_logs {
286 0 0   0   0 return if %logs;
287              
288             my $file_class = $ENV{PERL_TEST_DM_PROCESSES}
289 0 0 0     0 && $ENV{PERL_TEST_DM_PROCESSES} > 1 ? 'File::Locked' : 'File';
290              
291 0         0 for my $type (qw( status error prereq )) {
292 0         0 $logs{$type} = Log::Dispatch->new(
293             outputs => [
294             [
295             $file_class,
296             min_level => 'debug',
297             filename => _log_filename($type),
298             mode => 'append',
299             ],
300             ],
301             );
302             }
303             }
304              
305             sub _status_log {
306 0     0   0 $logs{status}->info(@_);
307             }
308              
309             sub _error_log {
310 0     0   0 $logs{error}->info(@_);
311             }
312              
313             sub _prereq_log {
314 0     0   0 $logs{prereq}->info(@_);
315             }
316             }
317              
318             sub _log_filename {
319 0     0   0 my $type = shift;
320              
321             return File::Spec->devnull
322 0 0       0 unless $ENV{PERL_TEST_DM_LOG_DIR};
323              
324             return File::Spec->catfile(
325             $ENV{PERL_TEST_DM_LOG_DIR},
326 0         0 'test-mydeps-' . $$ . q{-} . $type . '.log'
327             );
328             }
329              
330             sub _get_distro {
331 0     0   0 my $name = shift;
332              
333 0         0 my @mods = CPAN::Shell->expand( 'Module', $name );
334              
335 0 0       0 return unless @mods == 1;
336              
337 0         0 my $dist = $mods[0]->distribution;
338              
339 0 0       0 return unless $dist;
340              
341 0         0 $dist->get;
342              
343 0         0 return $dist;
344             }
345              
346             sub _install_prereqs {
347 0     0   0 my $dist = shift;
348 0   0     0 my $root_dist = shift || $dist->base_id;
349              
350 0         0 my $install_dir = _temp_lib_dir();
351              
352             ## no critic (Variables::RequireInitializationForLocalVars, Variables::ProhibitPackageVars)
353 0         0 local $CPAN::Config->{makepl_arg} .= " INSTALL_BASE=$install_dir";
354             local $CPAN::Config->{mbuild_install_arg}
355 0         0 .= " --install_base $install_dir";
356             ## use critic
357              
358 0         0 my $for_dist = $dist->base_id;
359              
360 0         0 for my $prereq ( $dist->unsat_prereq('configure_requires_later') ) {
361 0         0 _install_prereq( $prereq->[0], $for_dist, $root_dist );
362             }
363              
364 0         0 $dist->undelay;
365 0         0 $dist->make;
366              
367 0         0 for my $prereq ( $dist->unsat_prereq('later') ) {
368 0         0 _install_prereq( $prereq->[0], $for_dist, $root_dist );
369             }
370              
371 0         0 $dist->undelay;
372             }
373              
374             sub _install_prereq {
375 0     0   0 my $prereq = shift;
376 0         0 my $for_dist = shift;
377 0         0 my $root_dist = shift;
378              
379 0 0       0 return if $prereq eq 'perl';
380              
381 0         0 my $for = "for $for_dist";
382 0 0       0 if ( $for_dist ne $root_dist ) {
383 0         0 $for .= " (started with $root_dist)";
384             }
385              
386 0         0 my $dist = _get_distro($prereq);
387 0 0       0 if ( !$dist ) {
388 0         0 _prereq_log("Couldn't find $prereq $for\n");
389 0         0 next;
390             }
391              
392 0         0 _install_prereqs( $dist, $root_dist );
393              
394 0         0 my $installing = $dist->base_id;
395              
396 0         0 _prereq_log("Installing $installing $for\n");
397              
398             try {
399 0     0   0 $dist->notest;
400 0         0 $dist->install;
401             }
402             catch {
403 0     0   0 die "Installing $installing for $for_dist failed: $_";
404 0         0 };
405             }
406              
407             {
408             my $Dir;
409 1     1   8 BEGIN { $Dir = tempdir( CLEANUP => 1 ); }
410              
411             sub _temp_lib_dir {
412 1     1   12 return $Dir;
413             }
414             }
415              
416             sub _run_tests_for_dir {
417 7     7   1901270 my $dir = shift;
418              
419 7         162 local $CWD = $dir;
420              
421 7 100       781 if ( -e 'Build.PL' ) {
422             return
423 4 50       47 unless _run_commands(
424             ['./Build'],
425             );
426             }
427             else {
428             return
429 3 50       43 unless _run_commands(
430             ['make'],
431             );
432             }
433              
434 7         77 return _run_tests();
435             }
436              
437             sub _run_commands {
438 7     7   44 for my $cmd (@_) {
439 7         23 my $output;
440              
441             my $success = try {
442 7     7   559 run3( $cmd, \undef, \$output, \$output );
443             }
444             catch {
445 0     0   0 $output .= "Couldn't run @$cmd: $_";
446 0         0 return;
447 7         257 };
448              
449 7 50       3031905 return ( 0, $output )
450             unless $success;
451             }
452              
453 7         186 return 1;
454             }
455              
456             sub _run_tests {
457 7     7   43 my $output = q{};
458 7         31 my $error = q{};
459              
460             my $stderr = sub {
461 6     6   658870 my $line = shift;
462              
463 6         17 $output .= $line;
464 6         42 $error .= $line;
465 7         149 };
466              
467 7         31 my $cmd;
468 7 100       342 if ( -e 'Build' ) {
    50          
469 4         27 $cmd = [qw( ./Build test )];
470             }
471             elsif ( -e 'Makefile' ) {
472 3         19 $cmd = [qw( make test )];
473             }
474             else {
475 0         0 return ( 0, "Cannot find a Build or Makefile file in $CWD" );
476             }
477              
478 7         29 my $passed;
479             try {
480 7     7   592 run3( $cmd, undef, \$output, $stderr );
481 7 100       2618025 if ( $? == 0 ) {
482 6   33     269 $passed = $output eq q{}
483             || $output =~ /Result: (?:PASS|NOTESTS)|No tests defined/;
484             }
485             }
486             catch {
487 0     0   0 $output .= "Couldn't run @$cmd: $_";
488 0         0 $error .= "Couldn't run @$cmd: $_";
489 7         225 };
490              
491 7         744 return ( $passed, $output, $error );
492             }
493              
494             {
495             my $LOADED_CPAN = 0;
496              
497             sub _load_cpan {
498             ## no critic (TestingAndDebugging::ProhibitNoWarnings)
499 1     1   1366 no warnings 'once';
  1         2  
  1         92  
500 0 0   0     return if $LOADED_CPAN;
501              
502 0           require CPAN;
503 0           require CPAN::Shell;
504              
505             ## no critic (InputOutput::RequireBriefOpen)
506 0           open my $fh, '>', File::Spec->devnull;
507              
508             {
509 1     1   6 no warnings 'redefine';
  1         1  
  1         341  
  0            
510 0     0     *CPAN::Shell::report_fh = sub {$fh};
  0            
511             }
512              
513             ## no critic (Variables::ProhibitPackageVars)
514 0           $CPAN::Be_Silent = 1;
515              
516 0           CPAN::HandleConfig->load;
517 0           CPAN::Shell::setup_output();
518 0           CPAN::Index->reload('force');
519              
520 0           $CPAN::Config->{test_report} = 0;
521 0           $CPAN::Config->{mbuildpl_arg} .= ' --quiet';
522 0           $CPAN::Config->{prerequisites_policy} = 'follow';
523 0           $CPAN::Config->{make_install_make_command} =~ s/^sudo //;
524 0           $CPAN::Config->{mbuild_install_build_command} =~ s/^sudo //;
525 0           $CPAN::Config->{make_install_arg} =~ s/UNINST=1//;
526 0           $CPAN::Config->{mbuild_install_arg} =~ s/--uninst\s+1//;
527              
528 0 0         if ( $ENV{PERL_TEST_DM_CPAN_VERBOSE} ) {
529 0     0     $fh = io_from_write_cb( sub { $Test->diag( $_[0] ) } );
  0            
530             }
531              
532 0           $LOADED_CPAN = 1;
533              
534 0           return;
535             }
536             }
537              
538             1;
539              
540             # ABSTRACT: Test all modules which depend on your module
541              
542             __END__