File Coverage

blib/lib/CPAN/Reporter.pm
Criterion Covered Total %
statement 523 611 85.6
branch 226 306 73.8
condition 83 120 69.1
subroutine 59 61 96.7
pod 0 6 0.0
total 891 1104 80.7


line stmt bran cond sub pod time code
1 33     33   80505 use strict;
  33         35  
  33         1375  
2             package CPAN::Reporter;
3              
4             our $VERSION = '1.2017';
5              
6 33     33   106 use Config;
  33         32  
  33         1067  
7 33     33   13819 use Capture::Tiny qw/ capture tee_merged /;
  33         134974  
  33         1858  
8 33     33   143 use CPAN 1.94 ();
  33         492  
  33         509  
9             #CPAN.pm was split into separate files in this version
10             #set minimum to it for simplicity
11 33     33   12975 use CPAN::Version ();
  33         40078  
  33         695  
12 33     33   152 use File::Basename qw/basename dirname/;
  33         42  
  33         1561  
13 33     33   121 use File::Find ();
  33         36  
  33         332  
14 33     33   108 use File::HomeDir ();
  33         35  
  33         508  
15 33     33   93 use File::Path qw/mkpath rmtree/;
  33         31  
  33         1415  
16 33     33   109 use File::Spec 3.19 ();
  33         597  
  33         640  
17 33     33   98 use File::Temp 0.16 qw/tempdir/;
  33         519  
  33         1043  
18 33     33   1354 use IO::File ();
  33         1960  
  33         387  
19 33     33   13402 use Parse::CPAN::Meta ();
  33         24540  
  33         509  
20 33     33   824 use Probe::Perl ();
  33         1830  
  33         471  
21 33     33   4679 use Test::Reporter 1.54 ();
  33         23881  
  33         442  
22 33     33   13470 use CPAN::Reporter::Config ();
  33         72  
  33         687  
23 33     33   13306 use CPAN::Reporter::History ();
  33         74  
  33         647  
24 33     33   11553 use CPAN::Reporter::PrereqCheck ();
  33         67  
  33         623  
25              
26 33     33   132 use constant MAX_OUTPUT_LENGTH => 1_000_000;
  33         33  
  33         1868  
27              
28             #--------------------------------------------------------------------------#
29             # create temp lib dir for Devel::Autoflush
30             # so that PERL5OPT=-MDevel::Autoflush is found by any perl
31             #--------------------------------------------------------------------------#
32              
33 33     33   13393 use Devel::Autoflush 0.04 ();
  33         1074  
  33         176851  
34             # directory fixture
35             my $Autoflush_Lib = tempdir(
36               "CPAN-Reporter-lib-XXXX", TMPDIR => 1, CLEANUP => 1
37             );
38             # copy Devel::Autoflush to directory or clear autoflush_lib variable
39             _file_copy_quiet(
40               $INC{'Devel/Autoflush.pm'},
41               File::Spec->catfile( $Autoflush_Lib, qw/Devel Autoflush.pm/ )
42             ) or undef $Autoflush_Lib;
43              
44             #--------------------------------------------------------------------------#
45             # public API
46             #--------------------------------------------------------------------------#
47              
48             sub configure {
49 2     2 0 7825     goto &CPAN::Reporter::Config::_configure;
50             }
51              
52             sub grade_make {
53 16     16 0 1108     my @args = @_;
54 16 100       111     my $result = _init_result( 'make', @args ) or return;
55 15         72     _compute_make_grade($result);
56 15 100       57     if ( $result->{grade} eq 'discard' ) {
57                     $CPAN::Frontend->myprint(
58                         "\nCPAN::Reporter: test results were not valid, $result->{grade_msg}.\n\n",
59 4         92             $result->{prereq_pm}, "\n",
60                         "Test report will not be sent"
61                     );
62 4 100       211         CPAN::Reporter::History::_record_history( $result )
63                         if not CPAN::Reporter::History::_is_duplicate( $result );
64                 }
65                 else {
66 11         75         _print_grade_msg($result->{make_cmd}, $result);
67 11 100       56         if ( $result->{grade} ne 'pass' ) { _dispatch_report( $result ) }
  9         37  
68                 }
69 15         555     return $result->{success};
70             }
71              
72             sub grade_PL {
73 34     34 0 5376     my @args = @_;
74 34 100       213     my $result = _init_result( 'PL', @args ) or return;
75 33         94     _compute_PL_grade($result);
76 33 100       109     if ( $result->{grade} eq 'discard' ) {
77                     $CPAN::Frontend->myprint(
78                         "\nCPAN::Reporter: test results were not valid, $result->{grade_msg}.\n\n",
79 9         168             $result->{prereq_pm}, "\n",
80                         "Test report will not be sent"
81                     );
82 9 100       246         CPAN::Reporter::History::_record_history( $result )
83                         if not CPAN::Reporter::History::_is_duplicate( $result );
84                 }
85                 else {
86 24         145         _print_grade_msg($result->{PL_file} , $result);
87 24 100       73         if ( $result->{grade} ne 'pass' ) { _dispatch_report( $result ) }
  18         75  
88                 }
89 33         1187     return $result->{success};
90             }
91              
92             sub grade_test {
93 119     119 0 1926     my @args = @_;
94 119 100       766     my $result = _init_result( 'test', @args ) or return;
95 118         485     _compute_test_grade($result);
96 118 100       386     if ( $result->{grade} eq 'discard' ) {
97                     $CPAN::Frontend->myprint(
98                         "\nCPAN::Reporter: test results were not valid, $result->{grade_msg}.\n\n",
99 15         246             $result->{prereq_pm}, "\n",
100                         "Test report will not be sent"
101                     );
102 15 100       386         CPAN::Reporter::History::_record_history( $result )
103                         if not CPAN::Reporter::History::_is_duplicate( $result );
104                 }
105                 else {
106 103         553         _print_grade_msg( "Test", $result );
107 103         381         _dispatch_report( $result );
108                 }
109 118         4512     return $result->{success};
110             }
111              
112             sub record_command {
113 186     186 0 35281168     my ($command, $timeout) = @_;
114              
115             # XXX refactor this!
116             # Get configuration options
117 186 100       1469     if ( -r CPAN::Reporter::Config::_get_config_file() ) {
118 170         1024         my $config_obj = CPAN::Reporter::Config::_open_config_file();
119 170         256         my $config;
120 170 50       839         $config = CPAN::Reporter::Config::_get_config_options( $config_obj )
121                         if $config_obj;
122              
123 170   100     1413         $timeout ||= $config->{command_timeout}; # might still be undef
124                 }
125              
126 186         993     my ($cmd, $redirect) = _split_redirect($command);
127              
128             # Teeing a command loses its exit value so we must wrap the command
129             # and print the exit code so we can read it off of output
130 186         230     my $wrap_code;
131 186 100       433     if ( $timeout ) {
132 15 50       80         $wrap_code = $^O eq 'MSWin32'
133                                ? _timeout_wrapper_win32($cmd, $timeout)
134                                : _timeout_wrapper($cmd, $timeout);
135                 }
136             # if no timeout or timeout wrap code wasn't available
137 186 100       403     if ( ! $wrap_code ) {
138 171         387         my $safecmd = quotemeta($cmd);
139 171         610         $wrap_code = << "HERE";
140             my \$rc = system("$safecmd");
141             my \$ec = \$rc == -1 ? -1 : \$?;
142             print "($safecmd exited with \$ec)\\n";
143             HERE
144                 }
145              
146             # write code to a tempfile for execution
147 186         628     my $wrapper_name = _temp_filename( 'CPAN-Reporter-CW-' );
148 186 50       2213     my $wrapper_fh = IO::File->new( $wrapper_name, 'w' )
149                     or die "Could not create a wrapper for $cmd\: $!";
150              
151 186         30997     $wrapper_fh->print( $wrap_code );
152 186         2237     $wrapper_fh->close;
153              
154             # tee the command wrapper
155 186         7786     my @tee_input = ( Probe::Perl->find_perl_interpreter, $wrapper_name );
156 186 100       2810     push @tee_input, $redirect if defined $redirect;
157 186         242     my $tee_out;
158                 {
159             # ensure autoflush if we can
160 186 100       233       local $ENV{PERL5OPT} = _get_perl5opt() if _is_PL($command);
  186         701  
161 186     186   5903       $tee_out = tee_merged { system( @tee_input ) };
  186         101918146  
162                 }
163              
164             # cleanup
165 186 50       498645     unlink $wrapper_name unless $ENV{PERL_CR_NO_CLEANUP};
166              
167 186         19531     my @cmd_output = split qr{(?<=$/)}, $tee_out;
168 186 50       892     if ( ! @cmd_output ) {
169 0         0         $CPAN::Frontend->mywarn(
170                         "CPAN::Reporter: didn't capture command results for '$cmd'\n"
171                     );
172 0         0         return;
173                 }
174              
175             # extract the exit value
176 186         341     my $exit_value;
177 186 50       1465     if ( $cmd_output[-1] =~ m{exited with} ) {
178 186         1423         ($exit_value) = $cmd_output[-1] =~ m{exited with ([-0-9]+)};
179 186         402         pop @cmd_output;
180                 }
181              
182             # bail out on some errors
183 186 50       1196     if ( ! defined $exit_value ) {
    50          
184 0         0         $CPAN::Frontend->mywarn(
185                         "CPAN::Reporter: couldn't determine exit value for '$cmd'\n"
186                     );
187 0         0         return;
188                 }
189                 elsif ( $exit_value == -1 ) {
190 0         0         $CPAN::Frontend->mywarn(
191                         "CPAN::Reporter: couldn't execute '$cmd'\n"
192                     );
193 0         0         return;
194                 }
195              
196 186         2689     return \@cmd_output, $exit_value;
197             }
198              
199             sub test {
200 76     76 0 12868083     my ($dist, $system_command) = @_;
201 76         340     my ($output, $exit_value) = record_command( $system_command );
202 76         409     return grade_test( $dist, $system_command, $output, $exit_value );
203             }
204              
205             #--------------------------------------------------------------------------#
206             # private functions
207             #--------------------------------------------------------------------------#
208              
209             #--------------------------------------------------------------------------#
210             # _compute_make_grade
211             #--------------------------------------------------------------------------#
212              
213             sub _compute_make_grade {
214 15     15   30     my $result = shift;
215 15         27     my ($grade,$msg);
216 15 100       51     if ( $result->{exit_value} ) {
217 13         55         $result->{grade} = "unknown";
218 13         38         $result->{grade_msg} = "Stopped with an error"
219                 }
220                 else {
221 2         10         $result->{grade} = "pass";
222 2         8         $result->{grade_msg} = "No errors"
223                 }
224              
225 15         72     _downgrade_known_causes( $result );
226              
227 15         60     $result->{success} = $result->{grade} eq 'pass';
228 15         41     return;
229             }
230              
231             #--------------------------------------------------------------------------#
232             # _compute_PL_grade
233             #--------------------------------------------------------------------------#
234              
235             sub _compute_PL_grade {
236 33     33   41     my $result = shift;
237 33         55     my ($grade,$msg);
238 33 100       87     if ( $result->{exit_value} ) {
239 23         90         $result->{grade} = "unknown";
240 23         69         $result->{grade_msg} = "Stopped with an error"
241                 }
242                 else {
243 10         41         $result->{grade} = "pass";
244 10         29         $result->{grade_msg} = "No errors"
245                 }
246              
247 33         152     _downgrade_known_causes( $result );
248              
249 33         126     $result->{success} = $result->{grade} eq 'pass';
250 33         94     return;
251             }
252              
253             #--------------------------------------------------------------------------#
254             # _compute_test_grade
255             #
256             # Don't shortcut to unknown unless _has_tests because a custom
257             # Makefile.PL or Build.PL might define tests in a non-standard way
258             #
259             # With test.pl and 'make test', any t/*.t might pass Test::Harness, but
260             # test.pl might still fail, or there might only be test.pl,
261             # so use exit code directly
262             #
263             # Likewise, if we have recursive Makefile.PL, then we don't trust the
264             # reverse-order parsing and should just take the exit code directly
265             #
266             # Otherwise, parse in reverse order for Test::Harness output or a couple
267             # other significant strings and stop after the first match. Going in
268             # reverse and stopping is done to (hopefully) avoid picking up spurious
269             # results from any test output. But then we have to check for
270             # unsupported OS strings in case those were printed but were not fatal.
271             #--------------------------------------------------------------------------#
272              
273             sub _compute_test_grade {
274 118     118   175     my $result = shift;
275 118         174     my ($grade,$msg);
276 118         215     my $output = $result->{output};
277              
278             # In some cases, get a result straight from the exit code
279 118 100 100     1476     if ( $result->{is_make} && ( -f "test.pl" || _has_recursive_make() ) ) {
      66        
280 16 100       75         if ( $result->{exit_value} ) {
281 10         35             $grade = "fail";
282 10         28             $msg = "'make test' error detected";
283                     }
284                     else {
285 6         15             $grade = "pass";
286 6         18             $msg = "'make test' no errors";
287                     }
288                 }
289             # Otherwise, get a result from Test::Harness output
290                 else {
291             # figure out the right harness parser
292 102         493         _expand_result( $result );
293 102         369         my $harness_version = $result->{toolchain}{'Test::Harness'}{have};
294 102 50       1416         my $harness_parser = CPAN::Version->vgt($harness_version, '2.99_01')
295                                 ? \&_parse_tap_harness
296                                 : \&_parse_test_harness;
297             # parse lines in reverse
298 102         5920         for my $i ( reverse 0 .. $#{$output} ) {
  102         445  
299 276 100       1693             if ( $output->[$i] =~ m{No support for OS|OS unsupported}ims ) { # from any *.t file
    100          
300 6         18                 $grade = 'na';
301 6         14                 $msg = 'This platform is not supported';
302                         }
303                         elsif ( $output->[$i] =~ m{^.?No tests defined}ms ) { # from M::B
304 8         24                 $grade = 'unknown';
305 8         16                 $msg = 'No tests provided';
306                         }
307                         else {
308 262         460                 ($grade, $msg) = $harness_parser->( $output->[$i] );
309                         }
310 276 100       591             last if $grade;
311                     }
312             # fallback on exit value if no recognizable Test::Harness output
313 102 100       473         if ( ! $grade ) {
314 12 100       76             $grade = $result->{exit_value} ? "fail" : "pass";
315                         $msg = ( $result->{is_make} ? "'make test' " : "'Build test' " )
316 12 100       75                  . ( $result->{exit_value} ? "error detected" : "no errors");
    100          
317                     }
318                 }
319              
320 118         453     $result->{grade} = $grade;
321 118         327     $result->{grade_msg} = $msg;
322              
323 118         589     _downgrade_known_causes( $result );
324              
325                 $result->{success} = $result->{grade} eq 'pass'
326 118   100     774                        || $result->{grade} eq 'unknown';
327 118         293     return;
328             }
329              
330             #--------------------------------------------------------------------------#
331             # _dispatch_report
332             #
333             # Set up Test::Reporter and prompt user for edit, send
334             #--------------------------------------------------------------------------#
335              
336             sub _dispatch_report {
337 131     131   5868     my $result = shift;
338 131         373     my $phase = $result->{phase};
339              
340 131         575     $CPAN::Frontend->myprint(
341                     "CPAN::Reporter: preparing a CPAN Testers report for $result->{dist_name}\n"
342                 );
343              
344             # Get configuration options
345 131         1804     my $config_obj = CPAN::Reporter::Config::_open_config_file();
346 131         211     my $config;
347 131 100       713     $config = CPAN::Reporter::Config::_get_config_options( $config_obj )
348                     if $config_obj;
349 131 100       464     if ( ! $config->{email_from} ) {
350 5         12         $CPAN::Frontend->mywarn( << "EMAIL_REQUIRED");
351            
352             CPAN::Reporter: required 'email_from' option missing an email address, so
353             test report will not be sent. See documentation for configuration details.
354            
355             Even though CPAN Testers no longer uses email, this email address will
356             show up in the report and help identify the tester. This is required
357             for compatibility with tools that process legacy reports for analysis.
358            
359             EMAIL_REQUIRED
360 5         37         return;
361                 }
362              
363             # Need to know if this is a duplicate
364 126         554     my $is_duplicate = CPAN::Reporter::History::_is_duplicate( $result );
365              
366             # Abort if the distribution name is not formatted according to
367             # CPAN Testers requirements: Dist-Name-version.suffix
368             # Regex from CPAN-Testers should extract name, separator, version
369             # and extension
370                 my @format_checks = $result->{dist_basename} =~
371 126         1772         m{(.+)([\-\_])(v?\d.*)(\.(?:tar\.(?:gz|bz2)|tgz|zip))$}i;
372                 ;
373 126 100       284     if ( ! grep { length } @format_checks ) {
  492         727  
374 3         30         $CPAN::Frontend->mywarn( << "END_BAD_DISTNAME");
375            
376             CPAN::Reporter: the distribution name '$result->{dist_basename}' does not
377             appear to be packaged according to CPAN tester guidelines. Perhaps it is
378             not a normal CPAN distribution.
379            
380             Test report will not be sent.
381            
382             END_BAD_DISTNAME
383              
384             # record this as a discard, instead
385 3         40         $result->{grade} = 'discard';
386 3 50       20         CPAN::Reporter::History::_record_history( $result )
387                         if not $is_duplicate;
388 3         13         return;
389                 }
390              
391             # Gather 'expensive' data for the report
392 123         329     _expand_result( $result);
393              
394             # Skip if distribution name matches the send_skipfile
395 123 100 66     438     if ( $config->{send_skipfile} && -r $config->{send_skipfile} ) {
396 4         31         my $send_skipfile = IO::File->new( $config->{send_skipfile}, "r" );
397 4         316         my $dist_id = $result->{dist}->pretty_id;
398 4         62         while ( my $pattern = <$send_skipfile> ) {
399 11         15             chomp($pattern);
400             # ignore comments
401 11 100       29             next if substr($pattern,0,1) eq '#';
402             # if it doesn't match, continue with next pattern
403 7 100       100             next if $dist_id !~ /$pattern/i;
404             # if it matches, warn and return
405 3         23             $CPAN::Frontend->myprint( << "END_SKIP_DIST" );
406             CPAN::Reporter: '$dist_id' matched against the send_skipfile.
407            
408             Test report will not be sent.
409            
410             END_SKIP_DIST
411              
412 3         67             return;
413                     }
414                 }
415              
416             # Setup the test report
417 120         1670     my $tr = Test::Reporter->new;
418 120         1909     $tr->grade( $result->{grade} );
419 120         1182     $tr->distribution( $result->{dist_name} );
420             # Older Test::Reporter doesn't support distfile, but we need it for
421             # Metabase transport
422                 $tr->distfile( $result->{dist}->pretty_id )
423 120 50       1671       if $Test::Reporter::VERSION >= 1.54;
424              
425             # Skip if duplicate and not sending duplicates
426 120 100       1272     if ( $is_duplicate ) {
427 74 100       269         if ( _prompt( $config, "send_duplicates", $tr->grade) =~ /^n/ ) {
428 2         6             $CPAN::Frontend->myprint(<< "DUPLICATE_REPORT");
429            
430             CPAN::Reporter: this appears to be a duplicate report for the $phase phase:
431 2         8 @{[$tr->subject]}
432            
433             Test report will not be sent.
434            
435             DUPLICATE_REPORT
436              
437 2         83             return;
438                     }
439                 }
440              
441             # Set debug and transport options, if supported
442 118 50       401     $tr->debug( $config->{debug} ) if defined $config->{debug};
443 118         227     my $transport = $config->{transport};
444 118 50 33     707     unless ( defined $transport && length $transport ) {
445 0         0         $CPAN::Frontend->mywarn( << "TRANSPORT_REQUIRED");
446            
447             CPAN::Reporter: required 'transport' option missing so the test report
448             will not be sent. See documentation for configuration details.
449            
450             TRANSPORT_REQUIRED
451 0         0         return;
452                 }
453 118         485     my @transport_args = split " ", $transport;
454              
455             # special hack for Metabase arguments
456 118 100       343     if ($transport_args[0] eq 'Metabase') {
457 116         330         @transport_args = _validate_metabase_args(@transport_args);
458 116 50       358         unless (@transport_args) {
459 0         0             $CPAN::Frontend->mywarn( "Test report will not be sent.\n\n" );
460 0         0             return;
461                     }
462                 }
463              
464 118         178     eval { $tr->transport( @transport_args ) };
  118         407  
465 118 100       1759     if ($@) {
466 1         10         $CPAN::Frontend->mywarn(
467                         "CPAN::Reporter: problem with Test::Reporter transport: \n" .
468                         "$@\n" .
469                         "Test report will not be sent\n"
470                     );
471 1         19         return;
472                 }
473              
474             # prepare mail transport
475 117         591     $tr->from( $config->{email_from} );
476              
477             # Populate the test report
478 117         920     $tr->comments( _report_text( $result ) );
479 117         1018     $tr->via( 'CPAN::Reporter ' . $CPAN::Reporter::VERSION );
480              
481             # prompt for editing report
482 117 50       775     if ( _prompt( $config, "edit_report", $tr->grade ) =~ /^y/ ) {
483 0         0         my $editor = $config->{editor};
484 0 0       0         local $ENV{VISUAL} = $editor if $editor; ## no critic
485 0         0         $tr->edit_comments;
486                 }
487              
488             # send_*_report can override send_report
489 117 100       608     my $send_config = defined $config->{"send_$phase\_report"}
490                                 ? "send_$phase\_report"
491                                 : "send_report" ;
492 117 100       546     if ( _prompt( $config, $send_config, $tr->grade ) =~ /^y/ ) {
493 114         523         $CPAN::Frontend->myprint( "CPAN::Reporter: sending test report with '" . $tr->grade .
494                           "' via " . $transport_args[0] . "\n");
495 114 50       2546         if ( $tr->send() ) {
496 114 100       882             CPAN::Reporter::History::_record_history( $result )
497                             if not $is_duplicate;
498                     }
499                     else {
500 0         0             $CPAN::Frontend->mywarn( "CPAN::Reporter: " . $tr->errstr . "\n");
501              
502 0 0       0             if ( $config->{retry_submission} ) {
503 0         0                 sleep(3);
504              
505 0         0                 $CPAN::Frontend->mywarn( "CPAN::Reporter: second attempt\n");
506 0         0                 $tr->errstr('');
507              
508 0 0       0                 if ( $tr->send() ) {
509 0 0       0                     CPAN::Reporter::History::_record_history( $result )
510                                     if not $is_duplicate;
511                             }
512                             else {
513 0         0                     $CPAN::Frontend->mywarn( "CPAN::Reporter: " . $tr->errstr . "\n");
514                             }
515                         }
516              
517                     }
518                 }
519                 else {
520 3         11         $CPAN::Frontend->myprint("CPAN::Reporter: test report will not be sent\n");
521                 }
522              
523 117         853     return;
524             }
525              
526             sub _report_timeout {
527 129     129   215     my $result = shift;
528 129 100       464     if ($result->{exit_value} == 9) {
529 2         18         my $config_obj = CPAN::Reporter::Config::_open_config_file();
530 2         5         my $config;
531 2 50       11         $config = CPAN::Reporter::Config::_get_config_options( $config_obj )
532                         if $config_obj;
533              
534 2 50       10         if ($config->{'_store_problems_in_dir'}) {
535 0         0             my $distribution = $result->{dist}->base_id;
536 0         0             my $file = "e9.$distribution.${\(time)}.$$.log";
  0         0  
537 0 0       0             if (open my $to_log_fh, '>>', $config->{'_store_problems_in_dir'}.'/'.$file) {
538 0         0                 print $to_log_fh $distribution,"\n";
539 0         0                 print $to_log_fh "stage: ",$result->{phase},"\n";
540 0         0                 print $to_log_fh $Config{archname},"\n";
541 0         0                 print $to_log_fh _report_text( $result );
542                         } else {
543                             $CPAN::Frontend->mywarn( "CPAN::Reporter: writing ".
544 0         0                     $config->{'_store_problems_in_dir'}.'/'.$file. " failed\n");
545                         }
546                     }
547 2 50       11         if ($config->{'_problem_log'}) {
548 0         0             my $distribution = $result->{dist}->base_id;
549 0 0       0             if (open my $to_log_fh, '>>', $config->{'_problem_log'}) {
550 0         0                 print $to_log_fh "$result->{phase} $distribution $Config{archname}\n";
551                         } else {
552                             $CPAN::Frontend->mywarn( "CPAN::Reporter: writing ".
553 0         0                     $config->{'_store_problems_in_dir'}. " failed\n");
554                         }
555                     }
556                 }
557             }
558              
559             #--------------------------------------------------------------------------#
560             # _downgrade_known_causes
561             # Downgrade failure/unknown grade if we can determine a cause
562             # If platform not supported => 'na'
563             # If perl version is too low => 'na'
564             # If stated prereqs missing => 'discard'
565             #--------------------------------------------------------------------------#
566              
567             sub _downgrade_known_causes {
568 169     169   284     my ($result) = @_;
569 169         759     my ($grade, $output) = ( $result->{grade}, $result->{output} );
570 169   50     1314     my $msg = $result->{grade_msg} || q{};
571              
572             # shortcut unless fail/unknown; but PL might look like pass but actually
573             # have "OS Unsupported" messages if someone printed message and then
574             # did "exit 0"
575 169 100       490     return if $grade eq 'na';
576 163 100 100     759     return if $grade eq 'pass' && $result->{phase} ne 'PL';
577              
578             # get prereqs
579 129         360     _expand_result( $result );
580              
581 129         641     _report_timeout( $result );
582              
583             # if process was halted with a signal, just set for discard and return
584 129 100       409     if ( $result->{exit_value} & 127 ) {
585 2         5         $result->{grade} = 'discard';
586 2         7         $result->{grade_msg} = 'Command interrupted';
587 2         5         return;
588                 }
589              
590             # look for perl version error messages from various programs
591             # "Error evaling..." type errors happen on Perl < 5.006 when modules
592             # define their version with "our $VERSION = ..."
593 127         178     my ($harness_error, $version_error, $unsupported) ;
594 127         427     for my $line ( @$output ) {
595 3614 100 100     9411       if ( $result->{phase} eq 'test'
596                     && $line =~ m{open3: IO::Pipe: Can't spawn.*?TAP/Parser/Iterator/Process.pm}
597                   ) {
598 2         3         $harness_error++;
599 2         5         last;
600                   }
601 3612 50 66     30838       if( $line =~ /(?<!skipped: )Perl .*? required.*?--this is only/ims ||
      100        
      66        
      66        
      33        
602             #?<! - quick hack for https://github.com/cpan-testers/CPAN-Reporter/issues/23
603                     $line =~ /Perl version .*? or higher required\. We run/ims || #EU::MM
604                     $line =~ /ERROR: perl: Version .*? is installed, but we need version/ims ||
605                     $line =~ /ERROR: perl \(.*?\) is installed, but we need version/ims ||
606                     $line =~ /Error evaling version line 'BEGIN/ims ||
607                     $line =~ /Could not eval '/ims
608                   ) {
609 8         26         $version_error++;
610 8         19         last;
611                   }
612 3604 100       10181       if ( $line =~ /No support for OS|OS unsupported/ims ) {
613 6         20         $unsupported++;
614 6         19         last;
615                   }
616                 }
617              
618             # if the test harness had an error, discard the report
619 127 100 100     3344     if ( $harness_error ) {
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
      100        
620 2         5       $grade = 'discard';
621 2         6       $msg = 'Test harness failure';
622                 }
623             # check for explicit version error or just a perl version prerequisite
624                 elsif ( $version_error || $result->{prereq_pm} =~ m{^\s+!\s+perl\s}ims ) {
625 12         41         $grade = 'na';
626 12         30         $msg = 'Perl version too low';
627                 }
628             # check again for unsupported OS in case we took 'fail' from exit value
629                 elsif ( $unsupported ) {
630 6         22         $grade = 'na';
631 6         19         $msg = 'This platform is not supported';
632                 }
633             # check for Makefile without 'test' target; there are lots
634             # of variations on the error message, e.g. "target test", "target 'test'",
635             # "'test'", "`test'" and so on.
636                 elsif (
637                   $result->{is_make} && $result->{phase} eq 'test' && ! _has_test_target()
638                 ) {
639 1         2         $grade = 'unknown';
640 1         3         $msg = 'No make test target';
641                 }
642             # check the prereq report for missing or failure flag '!'
643                 elsif ( $grade ne 'pass' && $result->{prereq_pm} =~ m{n/a}ims ) {
644 14         42         $grade = 'discard';
645 14         127         $msg = "Prerequisite missing:\n$result->{prereq_pm}";
646                 }
647                 elsif ( $grade ne 'pass' && $result->{prereq_pm} =~ m{^\s+!}ims ) {
648 8         28         $grade = 'discard';
649 8         36         $msg = "Prerequisite version too low:\n$result->{prereq_pm}";
650                 }
651             # in PL stage -- if pass but no Makefile or Build, then this should
652             # be recorded as a discard
653                 elsif ( $result->{phase} eq 'PL' && $grade eq 'pass'
654                      && ! -f 'Makefile' && ! -f 'Build'
655                 ) {
656 2         13         $grade = 'discard';
657 2         5         $msg = 'No Makefile or Build file found';
658                 }
659                 elsif ( $result->{command} =~ /Build.*?-j/ ) {
660 2         5         $grade = 'discard';
661 2         5         $msg = '-j is not a valid option for Module::Build (upgrade your CPAN.pm)';
662                 }
663                 elsif (
664                   $result->{is_make} && $result->{phase} eq 'make' &&
665 39         77       grep { /Makefile out-of-date with respect to Makefile.PL/ } @$output
666                 ) {
667 1         5         $grade = 'discard';
668 1         3         $msg = 'Makefile out-of-date';
669                 }
670              
671             # store results
672 127         1787     $result->{grade} = $grade;
673 127         424     $result->{grade_msg} = $msg;
674              
675 127         252     return;
676             }
677              
678             #--------------------------------------------------------------------------#
679             # _expand_result - add expensive information like prerequisites and
680             # toolchain that should only be generated if a report will actually
681             # be sent
682             #--------------------------------------------------------------------------#
683              
684             sub _expand_result {
685 354     354   564     my $result = shift;
686 354 100       1314     return if $result->{expanded}++; # only do this once
687 167         812     $result->{prereq_pm} = _prereq_report( $result->{dist} );
688                 {
689             # mirror PERL5OPT as in record_command
690 167 100       272       local $ENV{PERL5OPT} = _get_perl5opt() if _is_PL($result->{command});
  167         818  
691 167         646       $result->{env_vars} = _env_report();
692                 }
693 167         690     $result->{special_vars} = _special_vars_report();
694 167         634     $result->{toolchain_versions} = _toolchain_report( $result );
695 167         1309     $result->{perl_version} = CPAN::Reporter::History::_format_perl_version();
696 167         607     return;
697             }
698              
699             #--------------------------------------------------------------------------#
700             # _env_report
701             #--------------------------------------------------------------------------#
702              
703             # Entries bracketed with "/" are taken to be a regex; otherwise literal
704             my @env_vars= qw(
705             /HARNESS/
706             /LC_/
707             /PERL/
708             /_TEST/
709             COMSPEC
710             INCLUDE
711             LANG
712             LANGUAGE
713             LD_LIBRARY_PATH
714             LIB
715             NUMBER_OF_PROCESSORS
716             PATH
717             PROCESSOR_IDENTIFIER
718             SHELL
719             TEMP
720             TERM
721             TMPDIR
722             );
723              
724             sub _env_report {
725 176     176   14293     my @vars_found;
726 176         719     for my $var ( @env_vars ) {
727 2992 100       5643         if ( $var =~ m{^/(.+)/$} ) {
728 704         3020             push @vars_found, grep { /$1/ } keys %ENV;
  16324         27785  
729                     }
730                     else {
731 2288 100       4100             push @vars_found, $var if exists $ENV{$var};
732                     }
733                 }
734              
735 176         325     my $report = "";
736 176         1224     for my $var ( sort @vars_found ) {
737 2145         2184         my $value = $ENV{$var};
738 2145 50       2526         $value = '[undef]' if ! defined $value;
739 2145         2989         $report .= " $var = $value\n";
740                 }
741 176         880     return $report;
742             }
743              
744             #--------------------------------------------------------------------------#
745             # _file_copy_quiet
746             #
747             # manual file copy -- quietly return undef on failure
748             #--------------------------------------------------------------------------#
749              
750             sub _file_copy_quiet {
751 33     33   66   my ($source, $target) = @_;
752             # ensure we have a target directory
753 33 50       5103   mkpath( dirname($target) ) or return;
754             # read source
755 33         104   local *FH;
756 33 50       1199   open FH, "<$source" or return; ## no critic
757 33         52   my $pm_guts = do { local $/; <FH> };
  33         120  
  33         477  
758 33         172   close FH;
759             # write target
760 33 50       1566   open FH, ">$target" or return; ## no critic
761 33         135   print FH $pm_guts;
762 33         967   close FH;
763 33         169   return 1;
764             }
765              
766             #--------------------------------------------------------------------------#
767             # _get_perl5opt
768             #--------------------------------------------------------------------------#
769              
770             sub _get_perl5opt {
771 76   50 76   469   my $perl5opt = $ENV{PERL5OPT} || q{};
772 76 50       189   if ( $Autoflush_Lib ) {
773 76 50       162     $perl5opt .= q{ } if length $perl5opt;
774 76 50       315     $perl5opt .= "-I$Autoflush_Lib " if $] >= 5.008;
775 76         129     $perl5opt .= "-MDevel::Autoflush";
776               }
777 76         659   return $perl5opt;
778             }
779              
780             #--------------------------------------------------------------------------#
781             # _has_recursive_make
782             #
783             # Ignore Makefile.PL in t directories
784             #--------------------------------------------------------------------------#
785              
786             sub _has_recursive_make {
787 67     67   106     my $PL_count = 0;
788                 File::Find::find(
789                     sub {
790 2318 100   2318   64787             if ( $_ eq 't' ) {
    100          
791 63         396                 $File::Find::prune = 1;
792                         }
793                         elsif ( $_ eq 'Makefile.PL' ) {
794 73         871                 $PL_count++;
795                         }
796                     },
797 67         6246         File::Spec->curdir()
798                 );
799 67         580     return $PL_count > 1;
800             }
801              
802             #--------------------------------------------------------------------------#
803             # _has_test_target
804             #--------------------------------------------------------------------------#
805              
806             sub _has_test_target {
807 47 50   47   680   my $fh = IO::File->new("Makefile") or return;
808 47         19042   return scalar grep { /^test[ ]*:/ } <$fh>;
  40793         34154  
809             }
810              
811             #--------------------------------------------------------------------------#
812             # _init_result -- create and return a hash of values for use in
813             # report evaluation and dispatch
814             #
815             # takes same argument format as grade_*()
816             #--------------------------------------------------------------------------#
817              
818             sub _init_result {
819 173     173   6338     my ($phase, $dist, $system_command, $output, $exit_value) = @_;
820              
821 173 50 33     1473     unless ( defined $output && defined $exit_value ) {
822 0         0         my $missing;
823 0 0 0     0         if ( ! defined $output && ! defined $exit_value ) {
    0 0        
824 0         0             $missing = "exit value and output"
825                     }
826                     elsif ( defined $output && !defined $exit_value ) {
827 0         0             $missing = "exit value"
828                     }
829                     else {
830 0         0             $missing = "output";
831                     }
832 0         0         $CPAN::Frontend->mywarn(
833                         "CPAN::Reporter: had errors capturing $missing. Tests abandoned"
834                     );
835 0         0         return;
836                 }
837              
838 173 100       1443     if ( $dist->pretty_id =~ m{\w+/Perl6/} ) {
839 3         31         $CPAN::Frontend->mywarn(
840                         "CPAN::Reporter: Won't report a Perl6 distribution."
841                     );
842 3         120         return;
843                 }
844              
845 170 100       1858     my $result = {
846                     phase => $phase,
847                     dist => $dist,
848                     command => $system_command,
849                     is_make => _is_make( $system_command ),
850                     output => ref $output eq 'ARRAY' ? $output : [ split /\n/, $output ],
851                     exit_value => $exit_value,
852             # Note: pretty_id is like "DAGOLDEN/CPAN-Reporter-0.40.tar.gz"
853                     dist_basename => basename($dist->pretty_id),
854                     dist_name => $dist->base_id,
855                 };
856              
857             # Used in messages to user
858 170 100       18872     $result->{PL_file} = $result->{is_make} ? "Makefile.PL" : "Build.PL";
859 170 100       2478     $result->{make_cmd} = $result->{is_make} ? $Config{make} : "Build";
860              
861             # CPAN might fail to find an author object for some strange dists
862 170         752     my $author = $dist->author;
863 170 50       1161     $result->{author} = defined $author ? $author->fullname : "Author";
864 170 50       1382     $result->{author_id} = defined $author ? $author->id : "" ;
865              
866 170         1340     return $result;
867             }
868              
869             #--------------------------------------------------------------------------#
870             # _is_make
871             #--------------------------------------------------------------------------#
872              
873             sub _is_make {
874 190     190   8617     my $command = shift;
875 190 100       2440     return $command =~ m{\b(?:\S*make|Makefile.PL)\b}ims ? 1 : 0;
876             }
877              
878             #--------------------------------------------------------------------------#
879             # _is_PL
880             #--------------------------------------------------------------------------#
881              
882             sub _is_PL {
883 353     353   660   my $command = shift;
884 353 100       3681   return $command =~ m{\b(?:Makefile|Build)\.PL\b}ims ? 1 : 0;
885             }
886              
887             #--------------------------------------------------------------------------#
888             # _max_length
889             #--------------------------------------------------------------------------#
890              
891             sub _max_length {
892 352     352   1288     my ($first, @rest) = @_;
893 352         475     my $max = length $first;
894 352         641     for my $term ( @rest ) {
895 6688 100       8151         $max = length $term if length $term > $max;
896                 }
897 352         807     return $max;
898             }
899              
900              
901             #--------------------------------------------------------------------------#
902             # _parse_tap_harness
903             #
904             # As of Test::Harness 2.99_02, the final line is provided by TAP::Harness
905             # as "Result: STATUS" where STATUS is "PASS", "FAIL" or "NOTESTS"
906             #--------------------------------------------------------------------------#
907              
908              
909             sub _parse_tap_harness {
910 262     262   330     my ($line) = @_;
911 262 100       1051     if ( $line =~ m{^Result:\s+([A-Z]+)} ) {
    100          
912 74 100       363         if ( $1 eq 'PASS' ) {
    100          
    50          
913 20         79             return ('pass', 'All tests successful');
914                     }
915                     elsif ( $1 eq 'FAIL' ) {
916 51         179             return ('fail', 'One or more tests failed');
917                     }
918                     elsif ( $1 eq 'NOTESTS' ) {
919 3         15             return ('unknown', 'No tests were run');
920                     }
921                 }
922                 elsif ( $line =~ m{Bailout called\.\s+Further testing stopped}ms ) {
923 2         7         return ( 'fail', 'Bailed out of tests');
924                 }
925 186         333     return;
926             }
927              
928             #--------------------------------------------------------------------------#
929             # _parse_test_harness
930             #
931             # Output strings taken from Test::Harness::
932             # _show_results() -- for versions < 2.57_03
933             # get_results() -- for versions >= 2.57_03
934             #--------------------------------------------------------------------------#
935              
936             sub _parse_test_harness {
937 0     0   0     my ($line) = @_;
938 0 0       0     if ( $line =~ m{^All tests successful}ms ) {
    0          
    0          
    0          
    0          
939 0         0         return ( 'pass', 'All tests successful' );
940                 }
941                 elsif ( $line =~ m{^FAILED--no tests were run}ms ) {
942 0         0         return ( 'unknown', 'No tests were run' );
943                 }
944                 elsif ( $line =~ m{^FAILED--.*--no output}ms ) {
945 0         0         return ( 'unknown', 'No tests were run');
946                 }
947                 elsif ( $line =~ m{FAILED--Further testing stopped}ms ) {
948 0         0         return ( 'fail', 'Bailed out of tests');
949                 }
950                 elsif ( $line =~ m{^Failed }ms ) { # must be lowercase
951 0         0         return ( 'fail', 'One or more tests failed');
952                 }
953                 else {
954 0         0         return;
955                 }
956             }
957              
958             #--------------------------------------------------------------------------#
959             # _prereq_report
960             #--------------------------------------------------------------------------#
961              
962             my @prereq_sections = qw(
963             requires build_requires configure_requires opt_requires opt_build_requires
964             );
965              
966             sub _prereq_report {
967 179     179   93701     my $dist = shift;
968 179         258     my (%need, %have, %prereq_met, $report);
969              
970             # Extract requires/build_requires from CPAN dist
971 179         604     my $prereq_pm = $dist->prereq_pm;
972              
973 179 50       1164     if ( ref $prereq_pm eq 'HASH' ) {
974             # CPAN 1.94 returns hash with requires/build_requires # so no need to support old style
975 179         776         foreach (values %$prereq_pm) {
976 711 50 66     2723           if (defined && ref ne 'HASH') {
977 0         0              require Data::Dumper;
978 0         0              warn "Data error detecting prerequisites. Please report it to CPAN::Reporter bug tracker:";
979 0         0              warn Data::Dumper::Dumper($prereq_pm);
980 0         0              die "Stopping";
981                       }
982                     }
983              
984 179         639         for my $sec ( @prereq_sections ) {
985 895 100       643             $need{$sec} = $prereq_pm->{$sec} if keys %{ $prereq_pm->{$sec} };
  895         3887  
986                     }
987                 }
988              
989             # Extract configure_requires from META.yml if it exists
990 179 100 66     1670     if ( $dist->{build_dir} && -d $dist->{build_dir} ) {
991 72         1232       my $meta_yml = File::Spec->catfile($dist->{build_dir}, 'META.yml');
992 72 100       901       if ( -f $meta_yml ) {
993 4         18         my @yaml = eval { Parse::CPAN::Meta::LoadFile($meta_yml) };
  4         35  
994 4 100       7895         if ( $@ ) {
995 2         28           $CPAN::Frontend->mywarn(
996                         "CPAN::Reporter: error parsing META.yml\n"
997                       );
998                     }
999 4 100 66     60         if ( ref $yaml[0] eq 'HASH' &&
1000                           ref $yaml[0]{configure_requires} eq 'HASH'
1001                     ) {
1002 2         15           $need{configure_requires} = $yaml[0]{configure_requires};
1003                     }
1004                   }
1005                 }
1006              
1007             # see what prereqs are satisfied in subprocess
1008 179         391     for my $section ( @prereq_sections ) {
1009 895 100       2233         next unless ref $need{$section} eq 'HASH';
1010 137         193         my @prereq_list = %{ $need{$section} };
  137         636  
1011 137 50       348         next unless @prereq_list;
1012 137         604         my $prereq_results = _version_finder( @prereq_list );
1013 137         270         for my $mod ( keys %{$prereq_results} ) {
  137         618  
1014 185         854             $have{$section}{$mod} = $prereq_results->{$mod}{have};
1015 185         990             $prereq_met{$section}{$mod} = $prereq_results->{$mod}{met};
1016                     }
1017                 }
1018              
1019             # find formatting widths
1020 179         431     my ($name_width, $need_width, $have_width) = (6, 4, 4);
1021 179         402     for my $section ( @prereq_sections ) {
1022 895         671         for my $module ( keys %{ $need{$section} } ) {
  895         2488  
1023 185         305             my $name_length = length $module;
1024 185         478             my $need_length = length $need{$section}{$module};
1025 185         258             my $have_length = length $have{$section}{$module};
1026 185 100       476             $name_width = $name_length if $name_length > $name_width;
1027 185 100       407             $need_width = $need_length if $need_length > $need_width;
1028 185 100       473             $have_width = $have_length if $have_length > $have_width;
1029                     }
1030                 }
1031              
1032 179         725     my $format_str =
1033                     " \%1s \%-${name_width}s \%-${need_width}s \%-${have_width}s\n";
1034              
1035             # generate the report
1036 179         351     for my $section ( @prereq_sections ) {
1037 895 100       634       if ( keys %{ $need{$section} } ) {
  895         1709  
1038 137         420         $report .= "$section:\n\n";
1039 137         829         $report .= sprintf( $format_str, " ", qw/Module Need Have/ );
1040 137         696         $report .= sprintf( $format_str, " ",
1041                       "-" x $name_width,
1042                       "-" x $need_width,
1043                       "-" x $have_width );
1044 137         207         for my $module (sort {lc $a cmp lc $b} keys %{ $need{$section} } ) {
  138         109  
  137         575  
1045 185         346           my $need = $need{$section}{$module};
1046 185         261           my $have = $have{$section}{$module};
1047 185 100       648           my $bad = $prereq_met{$section}{$module} ? " " : "!";
1048 185         612           $report .=
1049                       sprintf( $format_str, $bad, $module, $need, $have);
1050                     }
1051 137         246         $report .= "\n";
1052                   }
1053                 }
1054              
1055 179   100     2168     return $report || " No requirements found\n";
1056             }
1057              
1058             #--------------------------------------------------------------------------#
1059             # _print_grade_msg -
1060             #--------------------------------------------------------------------------#
1061              
1062             sub _print_grade_msg {
1063 138     138   642     my ($phase, $result) = @_;
1064 138         428     my ($grade, $msg) = ($result->{grade}, $result->{grade_msg});
1065 138         2461     $CPAN::Frontend->myprint( "CPAN::Reporter: $phase result is '$grade'");
1066 138 50 33     4147     $CPAN::Frontend->myprint(", $msg") if defined $msg && length $msg;
1067 138         1234     $CPAN::Frontend->myprint(".\n");
1068 138         887     return;
1069             }
1070              
1071             #--------------------------------------------------------------------------#
1072             # _prompt
1073             #
1074             # Note: always returns lowercase
1075             #--------------------------------------------------------------------------#
1076              
1077             sub _prompt {
1078 328     328   14577     my ($config, $option, $grade, $extra) = @_;
1079 328   50     1348     $extra ||= q{};
1080              
1081 328         732     my %spec = CPAN::Reporter::Config::_config_spec();
1082              
1083                 my $dispatch = CPAN::Reporter::Config::_validate_grade_action_pair(
1084 328   50     1778         $option, join(q{ }, "default:no", $config->{$option} || '')
1085                 );
1086 328   66     1102     my $action = $dispatch->{$grade} || $dispatch->{default};
1087 328         757     my $intro = $spec{$option}{prompt} . $extra . " (yes/no)";
1088 328         267     my $prompt;
1089 328 100       1109     if ( $action =~ m{^ask/yes}i ) {
    100          
1090 10         25         $prompt = CPAN::Shell::colorable_makemaker_prompt( $intro, "yes" );
1091                 }
1092                 elsif ( $action =~ m{^ask(/no)?}i ) {
1093 72         309         $prompt = CPAN::Shell::colorable_makemaker_prompt( $intro, "no" );
1094                 }
1095                 else {
1096 246         342         $prompt = $action;
1097                 }
1098 328         6405     return lc $prompt;
1099             }
1100              
1101             #--------------------------------------------------------------------------#
1102             # _report_text
1103             #--------------------------------------------------------------------------#
1104              
1105             my %intro_para = (
1106                 'pass' => <<'HERE',
1107             Thank you for uploading your work to CPAN. Congratulations!
1108             All tests were successful.
1109             HERE
1110              
1111                 'fail' => <<'HERE',
1112             Thank you for uploading your work to CPAN. However, there was a problem
1113             testing your distribution.
1114            
1115             If you think this report is invalid, please consult the CPAN Testers Wiki
1116             for suggestions on how to avoid getting FAIL reports for missing library
1117             or binary dependencies, unsupported operating systems, and so on:
1118            
1119             http://wiki.cpantesters.org/wiki/CPANAuthorNotes
1120             HERE
1121              
1122                 'unknown' => <<'HERE',
1123             Thank you for uploading your work to CPAN. However, attempting to
1124             test your distribution gave an inconclusive result.
1125            
1126             This could be because your distribution had an error during the make/build
1127             stage, did not define tests, tests could not be found, because your tests were
1128             interrupted before they finished, or because the results of the tests could not
1129             be parsed. You may wish to consult the CPAN Testers Wiki:
1130            
1131             http://wiki.cpantesters.org/wiki/CPANAuthorNotes
1132             HERE
1133              
1134                 'na' => <<'HERE',
1135             Thank you for uploading your work to CPAN. While attempting to build or test
1136             this distribution, the distribution signaled that support is not available
1137             either for this operating system or this version of Perl. Nevertheless, any
1138             diagnostic output produced is provided below for reference. If this is not
1139             what you expect, you may wish to consult the CPAN Testers Wiki:
1140            
1141             http://wiki.cpantesters.org/wiki/CPANAuthorNotes
1142             HERE
1143              
1144             );
1145              
1146             sub _comment_text {
1147              
1148             # We assemble the completed comment as a series of "parts" which
1149             # will get joined together
1150 117     117   131     my @comment_parts;
1151              
1152             # All automated testing gets a preamble
1153 117 100       371     if ($ENV{AUTOMATED_TESTING}) {
1154 111         261         push @comment_parts,
1155                         "this report is from an automated smoke testing program\n"
1156                         . "and was not reviewed by a human for accuracy"
1157                 }
1158              
1159             # If a comment file is provided, read it and add it to the comment
1160 117         277     my $confdir = CPAN::Reporter::Config::_get_config_dir();
1161 117         717     my $comment_file = File::Spec->catfile($confdir, 'comment.txt');
1162 117 100 66     2827     if ( -d $confdir && -f $comment_file && -r $comment_file ) {
      66        
1163 2 50   1   76         open my $fh, '<:encoding(UTF-8)', $comment_file or die($!);
  1         6  
  1         3  
  1         11  
1164 2         1372         my $text;
1165 2         3         do {
1166 2         6             local $/ = undef; # No record (line) seperator on input
1167 2 50       43             defined( $text = <$fh> ) or die($!);
1168                     };
1169 2         31         chomp($text);
1170 2         4         push @comment_parts, $text;
1171 2         21         close $fh;
1172                 }
1173              
1174             # If we have an empty comment so far, add a default value
1175 117 100       350     if (scalar(@comment_parts) == 0) {
1176 5         15         push @comment_parts, 'none provided';
1177                 }
1178              
1179             # Join the parts seperated by a blank line
1180 117         448     return join "\n\n", @comment_parts;
1181             }
1182              
1183             sub _report_text {
1184 117     117   141     my $data = shift;
1185 117         148     my $test_log = join(q{},@{$data->{output}});
  117         771  
1186 117 50       364     if ( length $test_log > MAX_OUTPUT_LENGTH ) {
1187 0         0         $test_log = substr( $test_log, 0, MAX_OUTPUT_LENGTH) . "\n";
1188 0         0         my $max_k = int(MAX_OUTPUT_LENGTH/1000) . "K";
1189 0         0         $test_log .= "\n[Output truncated after $max_k]\n\n";
1190                 }
1191              
1192 117         288     my $comment_body = _comment_text();
1193              
1194             # generate report
1195 117         1959     my $output = << "ENDREPORT";
1196             Dear $data->{author},
1197            
1198             This is a computer-generated report for $data->{dist_name}
1199             on perl $data->{perl_version}, created by CPAN-Reporter-$CPAN::Reporter::VERSION\.
1200            
1201             $intro_para{ $data->{grade} }
1202             Sections of this report:
1203            
1204             * Tester comments
1205             * Program output
1206             * Prerequisites
1207             * Environment and other context
1208            
1209             ------------------------------
1210             TESTER COMMENTS
1211             ------------------------------
1212            
1213             Additional comments from tester:
1214            
1215             $comment_body
1216            
1217             ------------------------------
1218             PROGRAM OUTPUT
1219             ------------------------------
1220            
1221             Output from '$data->{command}':
1222            
1223             $test_log
1224             ------------------------------
1225             PREREQUISITES
1226             ------------------------------
1227            
1228             Prerequisite modules loaded:
1229            
1230             $data->{prereq_pm}
1231             ------------------------------
1232             ENVIRONMENT AND OTHER CONTEXT
1233             ------------------------------
1234            
1235             Environment variables:
1236            
1237             $data->{env_vars}
1238             Perl special variables (and OS-specific diagnostics, for MSWin32):
1239            
1240             $data->{special_vars}
1241             Perl module toolchain versions installed:
1242            
1243             $data->{toolchain_versions}
1244             ENDREPORT
1245              
1246 117         453     return $output;
1247             }
1248              
1249             #--------------------------------------------------------------------------#
1250             # _special_vars_report
1251             #--------------------------------------------------------------------------#
1252              
1253             sub _special_vars_report {
1254 176     176   5387     my $special_vars = << "HERE";
1255             \$^X = $^X
1256             \$UID/\$EUID = $< / $>
1257             \$GID = $(
1258             \$EGID = $)
1259             HERE
1260 176 50 33     686     if ( $^O eq 'MSWin32' && eval "require Win32" ) { ## no critic
1261 0         0         my @getosversion = Win32::GetOSVersion();
1262 0         0         my $getosversion = join(", ", @getosversion);
1263 0         0         $special_vars .= " Win32::GetOSName = " . Win32::GetOSName() . "\n";
1264 0         0         $special_vars .= " Win32::GetOSVersion = $getosversion\n";
1265 0         0         $special_vars .= " Win32::FsType = " . Win32::FsType() . "\n";
1266 0         0         $special_vars .= " Win32::IsAdminUser = " . Win32::IsAdminUser() . "\n";
1267                 }
1268 176         641     return $special_vars;
1269             }
1270              
1271             #--------------------------------------------------------------------------#
1272             # _split_redirect
1273             #--------------------------------------------------------------------------#
1274              
1275             sub _split_redirect {
1276 186     186   302     my $command = shift;
1277 186         537     my ($cmd, $prefix) = ($command =~ m{\A(.+?)(\|.*)\z});
1278 186 100       399     if (defined $cmd) {
1279 1         4         return ($cmd, $prefix);
1280                 }
1281                 else { # didn't match a redirection
1282 185         420         return $command
1283                 }
1284             }
1285              
1286             #--------------------------------------------------------------------------#
1287             # _temp_filename -- stand-in for File::Temp for backwards compatibility
1288             #
1289             # takes an optional prefix, adds 8 random chars and returns
1290             # an absolute pathname
1291             #
1292             # NOTE -- manual unlink required
1293             #--------------------------------------------------------------------------#
1294              
1295             # @CHARS from File::Temp
1296             my @CHARS = (qw/ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
1297             a b c d e f g h i j k l m n o p q r s t u v w x y z
1298             0 1 2 3 4 5 6 7 8 9 _
1299             /);
1300              
1301             sub _temp_filename {
1302 500     500   1119     my ($prefix) = @_;
1303 500 50       1269     $prefix = q{} unless defined $prefix;
1304 500         5562     $prefix .= $CHARS[ int( rand(@CHARS) ) ] for 0 .. 7;
1305 500         11934     return File::Spec->catfile(File::Spec->tmpdir(), $prefix);
1306             }
1307              
1308             #--------------------------------------------------------------------------#
1309             # _timeout_wrapper
1310             # Timeout technique adapted from App::cpanminus (thank you Miyagawa!)
1311             #--------------------------------------------------------------------------#
1312              
1313             sub _timeout_wrapper {
1314 15     15   25     my ($cmd, $timeout) = @_;
1315              
1316             # protect shell quotes
1317 15         37     $cmd = quotemeta($cmd);
1318              
1319 15         103     my $wrapper = sprintf << 'HERE', $timeout, $cmd, $cmd;
1320             use strict;
1321             my ($pid, $exitcode);
1322             eval {
1323             $pid = fork;
1324             if ($pid) {
1325             local $SIG{CHLD};
1326             local $SIG{ALRM} = sub {die 'Timeout'};
1327             alarm %s;
1328             my $wstat = waitpid $pid, 0;
1329             alarm 0;
1330             $exitcode = $wstat == -1 ? -1 : $?;
1331             } elsif ( $pid == 0 ) {
1332             setpgrp(0,0); # new process group
1333             exec "%s";
1334             }
1335             else {
1336             die "Cannot fork: $!\n" unless defined $pid;
1337             }
1338             };
1339             if ($pid && $@ =~ /Timeout/){
1340             kill -9 => $pid; # and send to our child's whole process group
1341             waitpid $pid, 0;
1342             $exitcode = 9; # force result to look like SIGKILL
1343             }
1344             elsif ($@) {
1345             die $@;
1346             }
1347             print "(%s exited with $exitcode)\n";
1348             HERE
1349 15         34     return $wrapper;
1350             }
1351              
1352             #--------------------------------------------------------------------------#
1353             # _timeout_wrapper_win32
1354             #--------------------------------------------------------------------------#
1355              
1356             sub _timeout_wrapper_win32 {
1357 0     0   0     my ($cmd, $timeout) = @_;
1358              
1359 0   0     0     $timeout ||= 0; # just in case upstream doesn't guarantee it
1360              
1361 0         0     eval "use Win32::Job ();";
1362 0 0       0     if ($@) {
1363 0         0         $CPAN::Frontend->mywarn( << 'HERE' );
1364             CPAN::Reporter: you need Win32::Job for inactivity_timeout support.
1365             Continuing without timeout...
1366             HERE
1367 0         0         return;
1368                 }
1369              
1370 0         0     my ($program) = split " ", $cmd;
1371 0 0       0     if (! File::Spec->file_name_is_absolute( $program ) ) {
1372 0         0         my $exe = $program . ".exe";
1373 0         0         my ($path) = grep { -e File::Spec->catfile($_,$exe) }
1374 0         0                      split /$Config{path_sep}/, $ENV{PATH};
1375 0 0       0         if (! $path) {
1376 0         0             $CPAN::Frontend->mywarn( << "HERE" );
1377             CPAN::Reporter: can't locate $exe in the PATH.
1378             Continuing without timeout...
1379             HERE
1380 0         0             return;
1381                     }
1382 0         0         $program = File::Spec->catfile($path,$exe);
1383                 }
1384              
1385             # protect shell quotes and other things
1386 0         0     $_ = quotemeta($_) for ($program, $cmd);
1387              
1388 0         0     my $wrapper = sprintf << 'HERE', $program, $cmd, $timeout;
1389             use strict;
1390             use Win32::Job;
1391             my $executable = "%s";
1392             my $cmd_line = "%s";
1393             my $timeout = %s;
1394            
1395             my $job = Win32::Job->new() or die $^E;
1396             my $ppid = $job->spawn($executable, $cmd_line);
1397             $job->run($timeout);
1398             my $status = $job->status;
1399             my $exitcode = $status->{$ppid}{exitcode};
1400             if ( $exitcode == 293 ) {
1401             $exitcode = 9; # map Win32::Job kill (293) to SIGKILL (9)
1402             }
1403             elsif ( $exitcode & 255 ) {
1404             $exitcode = $exitcode << 8; # how perl expects it
1405             }
1406             print "($cmd_line exited with $exitcode)\n";
1407             HERE
1408 0         0     return $wrapper;
1409             }
1410              
1411             #--------------------------------------------------------------------------#-
1412             # _toolchain_report
1413             #--------------------------------------------------------------------------#
1414              
1415             my @toolchain_mods= qw(
1416             CPAN
1417             CPAN::Meta
1418             Cwd
1419             ExtUtils::CBuilder
1420             ExtUtils::Command
1421             ExtUtils::Install
1422             ExtUtils::MakeMaker
1423             ExtUtils::Manifest
1424             ExtUtils::ParseXS
1425             File::Spec
1426             JSON
1427             JSON::PP
1428             Module::Build
1429             Module::Signature
1430             Parse::CPAN::Meta
1431             Test::Harness
1432             Test::More
1433             YAML
1434             YAML::Syck
1435             version
1436             );
1437              
1438             sub _toolchain_report {
1439 176     176   1744     my ($result) = @_;
1440              
1441 176         486     my $installed = _version_finder( map { $_ => 0 } @toolchain_mods );
  3520         4059  
1442 176         2041     $result->{toolchain} = $installed;
1443              
1444 176         1588     my $mod_width = _max_length( keys %$installed );
1445                 my $ver_width = _max_length(
1446 176         751         map { $installed->{$_}{have} } keys %$installed
  3520         3442  
1447                 );
1448              
1449 176         905     my $format = " \%-${mod_width}s \%-${ver_width}s\n";
1450              
1451 176         424     my $report = "";
1452 176         1234     $report .= sprintf( $format, "Module", "Have" );
1453 176         801     $report .= sprintf( $format, "-" x $mod_width, "-" x $ver_width );
1454              
1455 176         2126     for my $var ( sort keys %$installed ) {
1456                     $report .= sprintf(" \%-${mod_width}s \%-${ver_width}s\n",
1457 3520         5745                             $var, $installed->{$var}{have} );
1458                 }
1459              
1460 176         1083     return $report;
1461             }
1462              
1463              
1464             #--------------------------------------------------------------------------#
1465             # _validate_metabase_args
1466             #
1467             # This is a kludge to make metabase transport args a little less
1468             # clunky for novice users
1469             #--------------------------------------------------------------------------#
1470              
1471             sub _validate_metabase_args {
1472 116     116   302     my @transport_args = @_;
1473 116         158     shift @transport_args; # drop leading 'Metabase'
1474 116         146     my (%args, $error);
1475              
1476 116 50       397     if ( @transport_args % 2 != 0 ) {
1477 0         0         $error = << "TRANSPORT_ARGS";
1478            
1479             CPAN::Reporter: Metabase 'transport' option had odd number of
1480             parameters in the config file. See documentation for proper
1481             configuration format.
1482            
1483             TRANSPORT_ARGS
1484                 }
1485                 else {
1486 116         554         %args = @transport_args;
1487              
1488 116         261         for my $key ( qw/uri id_file/ ) {
1489 232 50       572             if ( ! $args{$key} ) {
1490 0         0                 $error = << "TRANSPORT_ARGS";
1491            
1492             CPAN::Reporter: Metabase 'transport' option did not have
1493             a '$key' parameter in the config file. See documentation for
1494             proper configuration format.
1495            
1496             TRANSPORT_ARGS
1497                         }
1498                     }
1499                 }
1500              
1501 116 50       297     if ( $error ) {
1502 0         0         $CPAN::Frontend->mywarn( $error );
1503 0         0         return;
1504                 }
1505              
1506 116         372     $args{id_file} = CPAN::Reporter::Config::_normalize_id_file( $args{id_file} );
1507              
1508 116 50       1819     if ( ! -r $args{id_file} ) {
1509 0         0         $CPAN::Frontend->mywarn( <<"TRANSPORT_ARGS" );
1510            
1511             CPAN::Reporter: Could not find Metabase transport 'id_file' parameter
1512             located at '$args{id_file}'.
1513             See documentation for proper configuration of the 'transport' setting.
1514            
1515             TRANSPORT_ARGS
1516 0         0         return;
1517                 }
1518              
1519 116         688     return ('Metabase', %args);
1520             }
1521              
1522              
1523             #--------------------------------------------------------------------------#
1524             # _version_finder
1525             #
1526             # module => version pairs
1527             #
1528             # This is done via an external program to show installed versions exactly
1529             # the way they would be found when test programs are run. This means that
1530             # any updates to PERL5LIB will be reflected in the results.
1531             #
1532             # File-finding logic taken from CPAN::Module::inst_file(). Logic to
1533             # handle newer Module::Build prereq syntax is taken from
1534             # CPAN::Distribution::unsat_prereq()
1535             #
1536             #--------------------------------------------------------------------------#
1537              
1538             my $version_finder = $INC{'CPAN/Reporter/PrereqCheck.pm'};
1539              
1540             sub _version_finder {
1541 314     314   5212     my %prereqs = @_;
1542              
1543 314         4699     my $perl = Probe::Perl->find_perl_interpreter();
1544 314         6709     my @prereq_results;
1545              
1546 314         1107     my $prereq_input = _temp_filename( 'CPAN-Reporter-PI-' );
1547 314 50       4094     my $fh = IO::File->new( $prereq_input, "w" )
1548                     or die "Could not create temporary '$prereq_input' for prereq analysis: $!";
1549 314         61784     $fh->print( map { "$_ $prereqs{$_}\n" } keys %prereqs );
  3719         7660  
1550 314         4237     $fh->close;
1551              
1552 314     314   26533     my $prereq_result = capture { system( $perl, $version_finder, '<', $prereq_input ) };
  314         85240257  
1553              
1554 314         419075     unlink $prereq_input;
1555              
1556 314         724     my %result;
1557 314         2534     for my $line ( split "\n", $prereq_result ) {
1558 3719 50       5866         next unless length $line;
1559 3719         7668         my ($mod, $met, $have) = split " ", $line;
1560 3719 50 33     17634         unless ( defined($mod) && defined($met) && defined($have) ) {
      33        
1561 0         0             $CPAN::Frontend->mywarn(
1562                             "Error parsing output from CPAN::Reporter::PrereqCheck:\n" .
1563                             $line
1564                         );
1565 0         0             next;
1566                     }
1567 3719         11468         $result{$mod}{have} = $have;
1568 3719         5442         $result{$mod}{met} = $met;
1569                 }
1570 314         4523     return \%result;
1571             }
1572              
1573             1;
1574              
1575             # ABSTRACT: Adds CPAN Testers reporting to CPAN.pm
1576              
1577             =pod
1578            
1579             =encoding UTF-8
1580            
1581             =head1 NAME
1582            
1583             CPAN::Reporter - Adds CPAN Testers reporting to CPAN.pm
1584            
1585             =head1 VERSION
1586            
1587             version 1.2017
1588            
1589             =head1 SYNOPSIS
1590            
1591             From the CPAN shell:
1592            
1593             cpan> install Task::CPAN::Reporter
1594             cpan> reload cpan
1595             cpan> o conf init test_report
1596            
1597             Installing L<Task::CPAN::Reporter> will pull in additional dependencies
1598             that new CPAN Testers will need.
1599            
1600             Advanced CPAN Testers with custom L<Test::Reporter::Transport> setups
1601             may wish to install only CPAN::Reporter, which has fewer dependencies.
1602            
1603             =head1 DESCRIPTION
1604            
1605             The CPAN Testers project captures and analyzes detailed results from building
1606             and testing CPAN distributions on multiple operating systems and multiple
1607             versions of Perl. This provides valuable feedback to module authors and
1608             potential users to identify bugs or platform compatibility issues and improves
1609             the overall quality and value of CPAN.
1610            
1611             One way individuals can contribute is to send a report for each module that
1612             they test or install. CPAN::Reporter is an add-on for the CPAN.pm module to
1613             send the results of building and testing modules to the CPAN Testers project.
1614             Full support for CPAN::Reporter is available in CPAN.pm as of version 1.92.
1615            
1616             =for Pod::Coverage configure
1617             grade_PL
1618             grade_make
1619             grade_test
1620             record_command
1621             test
1622            
1623             =head1 GETTING STARTED
1624            
1625             =head2 Installation
1626            
1627             The first step in using CPAN::Reporter is to install it using whatever
1628             version of CPAN.pm is already installed. CPAN.pm will be upgraded as
1629             a dependency if necessary.
1630            
1631             cpan> install CPAN::Reporter
1632            
1633             If CPAN.pm was upgraded, it needs to be reloaded.
1634            
1635             cpan> reload cpan
1636            
1637             =head2 Configuration
1638            
1639             If upgrading from a very old version of CPAN.pm, users may be prompted to renew
1640             their configuration settings, including the 'test_report' option to enable
1641             CPAN::Reporter.
1642            
1643             If not prompted automatically, users should manually initialize CPAN::Reporter
1644             support. After enabling CPAN::Reporter, CPAN.pm will automatically continue
1645             with interactive configuration of CPAN::Reporter options.
1646            
1647             cpan> o conf init test_report
1648            
1649             Users will need to enter an email address in one of the following formats:
1650            
1651             johndoe@example.com
1652             John Doe <johndoe@example.com>
1653             "John Q. Public" <johnqpublic@example.com>
1654            
1655             Users that are new to CPAN::Reporter should accept the recommended values
1656             for other configuration options.
1657            
1658             Users will be prompted to create a I<Metabase profile> file that uniquely
1659             identifies their test reports. See L</"The Metabase"> below for details.
1660            
1661             After completing interactive configuration, be sure to commit (save) the CPAN
1662             configuration changes.
1663            
1664             cpan> o conf commit
1665            
1666             See L<CPAN::Reporter::Config> for advanced configuration settings.
1667            
1668             =head3 The Metabase
1669            
1670             CPAN::Reporter sends test reports to a server known as the Metabase. This
1671             requires an active Internet connection and a profile file. To create the
1672             profile, users will need to run C<<< metabase-profile >>> from a terminal window and
1673             fill the information at the prompts. This will create a file called
1674             C<<< metabase_id.json >>> in the current directory. That file should be moved to the
1675             C<<< .cpanreporter >>> directory inside the user's home directory.
1676            
1677             Users with an existing metabase profile file (e.g. from another machine),
1678             should copy it into the C<<< .cpanreporter >>> directory instead of creating
1679             a new one. Profile files may be located outside the C<<< .cpanreporter >>>
1680             directory by following instructions in L<CPAN::Reporter::Config>.
1681            
1682             =head3 Default Test Comments
1683            
1684             This module puts default text into the "TESTER COMMENTS" section, typically,
1685             "none provided" if doing interactive testing, or, if doing smoke testing that
1686             sets CE<lt>$ENV{AUTOMATED_TESTING}E<gt> to a true value, "this report is from an
1687             automated smoke testing program and was not reviewed by a human for
1688             accuracy." If CE<lt>CPAN::ReporterE<gt> is configured to allow editing of the
1689             report, this can be edited during submission.
1690            
1691             If you wish to override the default comment, you can create a file named
1692             CE<lt>comment.txtE<gt> in the configuration directory (typically C<<< .cpanreporter >>>
1693             under the user's home directory), with the default comment you would
1694             like to appear.
1695            
1696             Note that if your test is an automated smoke
1697             test (CE<lt>$ENV{AUTOMATED_TESTING}E<gt> is set to a true value), the smoke
1698             test notice ("this report is from an automated smoke testing program and
1699             was not reviewed by a human for accuracy") is included along with a blank
1700             line before your CE<lt>comment.txtE<gt>, so that it is always possible to
1701             distinguish automated tests from non-automated tests that use this
1702             module.
1703            
1704             =head2 Using CPAN::Reporter
1705            
1706             Once CPAN::Reporter is enabled and configured, test or install modules with
1707             CPAN.pm as usual.
1708            
1709             For example, to test the File::Marker module:
1710            
1711             cpan> test File::Marker
1712            
1713             If a distribution's tests fail, users will be prompted to edit the report to
1714             add additional information that might help the author understand the failure.
1715            
1716             =head1 UNDERSTANDING TEST GRADES
1717            
1718             CPAN::Reporter will assign one of the following grades to the report:
1719            
1720             =over
1721            
1722             =item *
1723            
1724             C<<< pass >>> -- distribution built and tested correctly
1725            
1726             =item *
1727            
1728             C<<< fail >>> -- distribution failed to test correctly
1729            
1730             =item *
1731            
1732             C<<< unknown >>> -- distribution failed to build, had no test suite or outcome was
1733             inconclusive
1734            
1735             =item *
1736            
1737             C<<< na >>> --- distribution is not applicable to this platform andE<sol>or
1738             version of Perl
1739            
1740             =back
1741            
1742             In returning results of the test suite to CPAN.pm, "pass" and "unknown" are
1743             considered successful attempts to "make test" or "Build test" and will not
1744             prevent installation. "fail" and "na" are considered to be failures and
1745             CPAN.pm will not install unless forced.
1746            
1747             An error from Makefile.PLE<sol>Build.PL or makeE<sol>Build will also be graded as
1748             "unknown" and a failure will be signaled to CPAN.pm.
1749            
1750             If prerequisites specified in C<<< Makefile.PL >>> or C<<< Build.PL >>> are not available,
1751             no report will be generated and a failure will be signaled to CPAN.pm.
1752            
1753             =head1 PRIVACY WARNING
1754            
1755             CPAN::Reporter includes information in the test report about environment
1756             variables and special Perl variables that could be affecting test results in
1757             order to help module authors interpret the results of the tests. This includes
1758             information about paths, terminal, locale, userE<sol>group ID, installed toolchain
1759             modules (e.g. ExtUtils::MakeMaker) and so on.
1760            
1761             These have been intentionally limited to items that should not cause harmful
1762             personal information to be revealed -- it does I<not> include your entire
1763             environment. Nevertheless, please do not use CPAN::Reporter if you are
1764             concerned about the disclosure of this information as part of your test report.
1765            
1766             Users wishing to review this information may choose to edit the report
1767             prior to sending it.
1768            
1769             =head1 BUGS
1770            
1771             Using command_timeout on Linux may cause problems. See
1772             L<https://rt.cpan.org/Ticket/Display.html?id=62310>
1773            
1774             Please report any bugs or feature using the CPAN Request Tracker.
1775             Bugs can be submitted through the web interface at
1776             L<http://rt.cpan.org/Dist/Display.html?Queue=CPAN-Reporter>
1777            
1778             When submitting a bug or request, please include a test-file or a patch to an
1779             existing test-file that illustrates the bug or desired feature.
1780            
1781             =head1 SEE ALSO
1782            
1783             Information about CPAN::Testers:
1784            
1785             =over
1786            
1787             =item *
1788            
1789             L<CPAN::Testers> -- overview of CPAN Testers architecture stack
1790            
1791             =item *
1792            
1793             L<http://www.cpantesters.org> -- project home with all reports
1794            
1795             =item *
1796            
1797             L<http://wiki.cpantesters.org> -- documentation and wiki
1798            
1799             =back
1800            
1801             Additional Documentation:
1802            
1803             =over
1804            
1805             =item *
1806            
1807             L<CPAN::Reporter::Config> -- advanced configuration settings
1808            
1809             =item *
1810            
1811             L<CPAN::Reporter::FAQ> -- hints and tips
1812            
1813             =back
1814            
1815             =for :stopwords cpan testmatrix url annocpan anno bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
1816            
1817             =head1 SUPPORT
1818            
1819             =head2 Bugs / Feature Requests
1820            
1821             Please report any bugs or feature requests through the issue tracker
1822             at L<https://github.com/cpan-testers/CPAN-Reporter/issues>.
1823             You will be notified automatically of any progress on your issue.
1824            
1825             =head2 Source Code
1826            
1827             This is open source software. The code repository is available for
1828             public review and contribution under the terms of the license.
1829            
1830             L<https://github.com/cpan-testers/CPAN-Reporter>
1831            
1832             git clone https://github.com/cpan-testers/CPAN-Reporter.git
1833            
1834             =head1 AUTHOR
1835            
1836             David Golden <dagolden@cpan.org>
1837            
1838             =head1 CONTRIBUTORS
1839            
1840             =for stopwords Alexandr Ciornii Breno G. de Oliveira Christian Walde Ed J Joel Maslak Kent Fredric Matthew Musgrove Patrice Clement Reini Urban Scott Wiersdorf Slaven Rezic
1841            
1842             =over 4
1843            
1844             =item *
1845            
1846             Alexandr Ciornii <alexchorny@gmail.com>
1847            
1848             =item *
1849            
1850             Breno G. de Oliveira <garu@cpan.org>
1851            
1852             =item *
1853            
1854             Christian Walde <walde.christian@googlemail.com>
1855            
1856             =item *
1857            
1858             Ed J <mohawk2@users.noreply.github.com>
1859            
1860             =item *
1861            
1862             Joel Maslak <jmaslak@antelope.net>
1863            
1864             =item *
1865            
1866             Kent Fredric <kentfredric@gmail.com>
1867            
1868             =item *
1869            
1870             Matthew Musgrove <mr.muskrat@gmail.com>
1871            
1872             =item *
1873            
1874             Patrice Clement <monsieurp@gentoo.org>
1875            
1876             =item *
1877            
1878             Reini Urban <rurban@cpanel.net>
1879            
1880             =item *
1881            
1882             Scott Wiersdorf <scott@perlcode.org>
1883            
1884             =item *
1885            
1886             Slaven Rezic <slaven@rezic.de>
1887            
1888             =back
1889            
1890             =head1 COPYRIGHT AND LICENSE
1891            
1892             This software is Copyright (c) 2006 by David Golden.
1893            
1894             This is free software, licensed under:
1895            
1896             The Apache License, Version 2.0, January 2004
1897            
1898             =cut
1899              
1900             __END__
1901            
1902            
1903             # vim: ts=4 sts=4 sw=4 et:
1904