File Coverage

blib/lib/CPAN/Reporter.pm
Criterion Covered Total %
statement 523 611 85.6
branch 228 308 74.0
condition 85 120 70.8
subroutine 59 61 96.7
pod 0 6 0.0
total 895 1106 80.9


line stmt bran cond sub pod time code
1 33     33   27258069 use strict;
  33         86  
  33         2446  
2             package CPAN::Reporter;
3              
4             our $VERSION = '1.2020';
5              
6 33     33   300 use Config;
  33         150  
  33         1981  
7 33     33   855 use Capture::Tiny qw/ capture tee_merged /;
  33         6851  
  33         3812  
8 33     33   222 use CPAN 1.94 ();
  33         865  
  33         1037  
9             #CPAN.pm was split into separate files in this version
10             #set minimum to it for simplicity
11 33     33   18130 use CPAN::Version ();
  33         59018  
  33         1284  
12 33     33   295 use File::Basename qw/basename dirname/;
  33         68  
  33         2443  
13 33     33   218 use File::Find ();
  33         100  
  33         576  
14 33     33   212 use File::HomeDir ();
  33         83  
  33         835  
15 33     33   167 use File::Path qw/mkpath rmtree/;
  33         97  
  33         2187  
16 33     33   215 use File::Spec 3.19 ();
  33         851  
  33         1063  
17 33     33   204 use File::Temp 0.16 qw/tempdir/;
  33         826  
  33         1608  
18 33     33   1508 use IO::File ();
  33         3037  
  33         525  
19 33     33   172 use Parse::CPAN::Meta ();
  33         78  
  33         509  
20 33     33   1055 use Probe::Perl ();
  33         3039  
  33         726  
21 33     33   1920 use Test::Reporter 1.54 ();
  33         39561  
  33         722  
22 33     33   22902 use CPAN::Reporter::Config ();
  33         192  
  33         1280  
23 33     33   22289 use CPAN::Reporter::History ();
  33         155  
  33         1219  
24 33     33   20172 use CPAN::Reporter::PrereqCheck ();
  33         126  
  33         1120  
25              
26 33     33   245 use constant MAX_OUTPUT_LENGTH => 1_000_000;
  33         64  
  33         3789  
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   19417 use Devel::Autoflush 0.04 ();
  33         1902  
  33         354464  
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 13313 goto &CPAN::Reporter::Config::_configure;
50             }
51              
52             sub grade_make {
53 16     16 0 3882 my @args = @_;
54 16 100       212 my $result = _init_result( 'make', @args ) or return;
55 15         156 _compute_make_grade($result);
56 15 100       104 if ( $result->{grade} eq 'discard' ) {
57             $CPAN::Frontend->myprint(
58             "\nCPAN::Reporter: test results were not valid, $result->{grade_msg}.\n\n",
59 4         198 $result->{prereq_pm}, "\n",
60             "Test report will not be sent"
61             );
62 4 100       292 CPAN::Reporter::History::_record_history( $result )
63             if not CPAN::Reporter::History::_is_duplicate( $result );
64             }
65             else {
66 11         121 _print_grade_msg($result->{make_cmd}, $result);
67 11 100       73 if ( $result->{grade} ne 'pass' ) { _dispatch_report( $result ) }
  9         122  
68             }
69 15         1230 return $result->{success};
70             }
71              
72             sub grade_PL {
73 34     34 0 14963 my @args = @_;
74 34 100       457 my $result = _init_result( 'PL', @args ) or return;
75 33         298 _compute_PL_grade($result);
76 33 100       165 if ( $result->{grade} eq 'discard' ) {
77             $CPAN::Frontend->myprint(
78             "\nCPAN::Reporter: test results were not valid, $result->{grade_msg}.\n\n",
79 9         407 $result->{prereq_pm}, "\n",
80             "Test report will not be sent"
81             );
82 9 100       1234 CPAN::Reporter::History::_record_history( $result )
83             if not CPAN::Reporter::History::_is_duplicate( $result );
84             }
85             else {
86 24         307 _print_grade_msg($result->{PL_file} , $result);
87 24 100       173 if ( $result->{grade} ne 'pass' ) { _dispatch_report( $result ) }
  18         242  
88             }
89 33         5159 return $result->{success};
90             }
91              
92             sub grade_test {
93 119     119 0 5535 my @args = @_;
94 119 100       1465 my $result = _init_result( 'test', @args ) or return;
95 118         1152 _compute_test_grade($result);
96 118 100       503 if ( $result->{grade} eq 'discard' ) {
97             $CPAN::Frontend->myprint(
98             "\nCPAN::Reporter: test results were not valid, $result->{grade_msg}.\n\n",
99 15         961 $result->{prereq_pm}, "\n",
100             "Test report will not be sent"
101             );
102 15 100       1241 CPAN::Reporter::History::_record_history( $result )
103             if not CPAN::Reporter::History::_is_duplicate( $result );
104             }
105             else {
106 103         1189 _print_grade_msg( "Test", $result );
107 103         769 _dispatch_report( $result );
108             }
109 118         16358 return $result->{success};
110             }
111              
112             sub record_command {
113 186     186 0 312626779 my ($command, $timeout) = @_;
114              
115             # XXX refactor this!
116             # Get configuration options
117 186 100       3289 if ( -r CPAN::Reporter::Config::_get_config_file() ) {
118 170         2230 my $config_obj = CPAN::Reporter::Config::_open_config_file();
119 170         426 my $config;
120 170 50       2886 $config = CPAN::Reporter::Config::_get_config_options( $config_obj )
121             if $config_obj;
122              
123 170   100     2692 $timeout ||= $config->{command_timeout}; # might still be undef
124             }
125              
126 186         2033 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         457 my $wrap_code;
131 186 100       691 if ( $timeout ) {
132 15 50       122 $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       772 if ( ! $wrap_code ) {
138 171         758 my $safecmd = quotemeta($cmd);
139 171         858 $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         1083 my $wrapper_name = _temp_filename( 'CPAN-Reporter-CW-' );
148 186 50       4513 my $wrapper_fh = IO::File->new( $wrapper_name, 'w' )
149             or die "Could not create a wrapper for $cmd\: $!";
150              
151 186         78366 $wrapper_fh->print( $wrap_code );
152 186         4536 $wrapper_fh->close;
153              
154             # tee the command wrapper
155 186         19109 my @tee_input = ( Probe::Perl->find_perl_interpreter, $wrapper_name );
156 186 100       5348 push @tee_input, $redirect if defined $redirect;
157 186         429 my $tee_out;
158             {
159             # ensure autoflush if we can
160 186 100       460 local $ENV{PERL5OPT} = _get_perl5opt() if _is_PL($command);
  186         1242  
161 186     186   10529 $tee_out = tee_merged { system( @tee_input ) };
  186         772073724  
162             }
163              
164             # cleanup
165 186 50       25009243 unlink $wrapper_name unless $ENV{PERL_CR_NO_CLEANUP};
166              
167 186         40750 my @cmd_output = split qr{(?<=$/)}, $tee_out;
168 186 50       1560 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         731 my $exit_value;
177 186 50       3339 if ( $cmd_output[-1] =~ m{exited with} ) {
178 186         2776 ($exit_value) = $cmd_output[-1] =~ m{exited with ([-0-9]+)};
179 186         714 pop @cmd_output;
180             }
181              
182             # bail out on some errors
183 186 50       1662 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         10318 return \@cmd_output, $exit_value;
197             }
198              
199             sub test {
200 76     76 0 108721755 my ($dist, $system_command) = @_;
201 76         789 my ($output, $exit_value) = record_command( $system_command );
202 76         685 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   44 my $result = shift;
215 15         922 my ($grade,$msg);
216 15 100       71 if ( $result->{exit_value} ) {
217 13         127 $result->{grade} = "unknown";
218 13         63 $result->{grade_msg} = "Stopped with an error"
219             }
220             else {
221 2         22 $result->{grade} = "pass";
222 2         12 $result->{grade_msg} = "No errors"
223             }
224              
225 15         149 _downgrade_known_causes( $result );
226              
227 15         122 $result->{success} = $result->{grade} eq 'pass';
228 15         137 return;
229             }
230              
231             #--------------------------------------------------------------------------#
232             # _compute_PL_grade
233             #--------------------------------------------------------------------------#
234              
235             sub _compute_PL_grade {
236 33     33   93 my $result = shift;
237 33         318 my ($grade,$msg);
238 33 100       296 if ( $result->{exit_value} ) {
239 23         193 $result->{grade} = "unknown";
240 23         271 $result->{grade_msg} = "Stopped with an error"
241             }
242             else {
243 10         102 $result->{grade} = "pass";
244 10         50 $result->{grade_msg} = "No errors"
245             }
246              
247 33         298 _downgrade_known_causes( $result );
248              
249 33         337 $result->{success} = $result->{grade} eq 'pass';
250 33         199 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   387 my $result = shift;
275 118         411 my ($grade,$msg);
276 118         538 my $output = $result->{output};
277              
278             # In some cases, get a result straight from the exit code
279 118 100 100     2625 if ( $result->{is_make} && ( -f "test.pl" || _has_recursive_make() ) ) {
      100        
280 16 100       192 if ( $result->{exit_value} ) {
281 10         288 $grade = "fail";
282 10         71 $msg = "'make test' error detected";
283             }
284             else {
285 6         165 $grade = "pass";
286 6         28 $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         1247 _expand_result( $result );
293 102         719 my $harness_version = $result->{toolchain}{'Test::Harness'}{have};
294 102 50       3428 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         15554 for my $i ( reverse 0 .. $#{$output} ) {
  102         965  
299 266 100       3472 if ( $output->[$i] =~ m{No support for OS|OS unsupported}ims ) { # from any *.t file
    100          
300 6         78 $grade = 'na';
301 6         56 $msg = 'This platform is not supported';
302             }
303             elsif ( $output->[$i] =~ m{^.?No tests defined}ms ) { # from M::B
304 8         69 $grade = 'unknown';
305 8         29 $msg = 'No tests provided';
306             }
307             else {
308 252         859 ($grade, $msg) = $harness_parser->( $output->[$i] );
309             }
310 266 100       922 last if $grade;
311             }
312             # fallback on exit value if no recognizable Test::Harness output
313 102 100       549 if ( ! $grade ) {
314 12 100       174 $grade = $result->{exit_value} ? "fail" : "pass";
315             $msg = ( $result->{is_make} ? "'make test' " : "'Build test' " )
316 12 100       155 . ( $result->{exit_value} ? "error detected" : "no errors");
    100          
317             }
318             }
319              
320 118         858 $result->{grade} = $grade;
321 118         711 $result->{grade_msg} = $msg;
322              
323 118         1094 _downgrade_known_causes( $result );
324              
325             $result->{success} = $result->{grade} eq 'pass'
326 118   100     1523 || $result->{grade} eq 'unknown';
327 118         435 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   11691 my $result = shift;
338 131         734 my $phase = $result->{phase};
339              
340 131         1111 $CPAN::Frontend->myprint(
341             "CPAN::Reporter: preparing a CPAN Testers report for $result->{dist_name}\n"
342             );
343              
344             # Get configuration options
345 131         4375 my $config_obj = CPAN::Reporter::Config::_open_config_file();
346 131         352 my $config;
347 131 100       1279 $config = CPAN::Reporter::Config::_get_config_options( $config_obj )
348             if $config_obj;
349 131 100       692 if ( ! $config->{email_from} ) {
350 5         28 $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         162 return;
361             }
362              
363             # Need to know if this is a duplicate
364 126         1250 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         2633 m{(.+)([\-\_])(v?\d.*)(\.(?:tar\.(?:gz|bz2)|tgz|zip))$}i;
372             ;
373 126 100       426 if ( ! grep { length } @format_checks ) {
  492         1107  
374 3         105 $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         126 $result->{grade} = 'discard';
386 3 50       30 CPAN::Reporter::History::_record_history( $result )
387             if not $is_duplicate;
388 3         29 return;
389             }
390              
391             # Gather 'expensive' data for the report
392 123         589 _expand_result( $result);
393              
394             # Skip if distribution name matches the send_skipfile
395 123 100 66     665 if ( $config->{send_skipfile} && -r $config->{send_skipfile} ) {
396 4         56 my $send_skipfile = IO::File->new( $config->{send_skipfile}, "r" );
397 4         793 my $dist_id = $result->{dist}->pretty_id;
398 4         108 while ( my $pattern = <$send_skipfile> ) {
399 11         18 chomp($pattern);
400             # ignore comments
401 11 100       38 next if substr($pattern,0,1) eq '#';
402             # if it doesn't match, continue with next pattern
403 7 100       163 next if $dist_id !~ /$pattern/i;
404             # if it matches, warn and return
405 3         32 $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         165 return;
413             }
414             }
415              
416             # Setup the test report
417 120         3877 my $tr = Test::Reporter->new;
418 120         3761 $tr->grade( $result->{grade} );
419 120         2047 $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       3344 if $Test::Reporter::VERSION >= 1.54;
424              
425             # Skip if duplicate and not sending duplicates
426 120 100       6229 if ( $is_duplicate ) {
427 74 100       620 if ( _prompt( $config, "send_duplicates", $tr->grade) =~ /^n/ ) {
428 2         8 $CPAN::Frontend->myprint(<< "DUPLICATE_REPORT");
429              
430             CPAN::Reporter: this appears to be a duplicate report for the $phase phase:
431 2         12 @{[$tr->subject]}
432              
433             Test report will not be sent.
434              
435             DUPLICATE_REPORT
436              
437 2         156 return;
438             }
439             }
440              
441             # Set debug and transport options, if supported
442 118 50       553 $tr->debug( $config->{debug} ) if defined $config->{debug};
443 118         351 my $transport = $config->{transport};
444 118 50 33     1122 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         855 my @transport_args = split " ", $transport;
454              
455             # special hack for Metabase arguments
456 118 100       483 if ($transport_args[0] eq 'Metabase') {
457 116         574 @transport_args = _validate_metabase_args(@transport_args);
458 116 50       449 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         267 eval { $tr->transport( @transport_args ) };
  118         701  
465 118 100       3223 if ($@) {
466 1         17 $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         34 return;
472             }
473              
474             # prepare mail transport
475 117         1154 $tr->from( $config->{email_from} );
476              
477             # Populate the test report
478 117         1590 $tr->comments( _report_text( $result ) );
479 117         2227 $tr->via( 'CPAN::Reporter ' . $CPAN::Reporter::VERSION );
480              
481             # prompt for editing report
482 117 50       1344 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       840 my $send_config = defined $config->{"send_$phase\_report"}
490             ? "send_$phase\_report"
491             : "send_report" ;
492 117 100       1154 if ( _prompt( $config, $send_config, $tr->grade ) =~ /^y/ ) {
493 114         738 $CPAN::Frontend->myprint( "CPAN::Reporter: sending test report with '" . $tr->grade .
494             "' via " . $transport_args[0] . "\n");
495 114 50       8002 if ( $tr->send() ) {
496 114 100       1655 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         37 $CPAN::Frontend->myprint("CPAN::Reporter: test report will not be sent\n");
521             }
522              
523 117         1922 return;
524             }
525              
526             sub _report_timeout {
527 129     129   595 my $result = shift;
528 129 100       779 if ($result->{exit_value} == 9) {
529 2         4288 my $config_obj = CPAN::Reporter::Config::_open_config_file();
530 2         14 my $config;
531 2 50       33 $config = CPAN::Reporter::Config::_get_config_options( $config_obj )
532             if $config_obj;
533              
534 2 50       20 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       24 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   676 my ($result) = @_;
569 169         1053 my ($grade, $output) = ( $result->{grade}, $result->{output} );
570 169   50     911 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       715 return if $grade eq 'na';
576 163 100 100     1256 return if $grade eq 'pass' && $result->{phase} ne 'PL';
577              
578             # get prereqs
579 129         814 _expand_result( $result );
580              
581 129         1186 _report_timeout( $result );
582              
583             # if process was halted with a signal, just set for discard and return
584 129 100       780 if ( $result->{exit_value} & 127 ) {
585 2         17 $result->{grade} = 'discard';
586 2         16 $result->{grade_msg} = 'Command interrupted';
587 2         230 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         469 my ($harness_error, $version_error, $unsupported) ;
594 127         749 for my $line ( @$output ) {
595 3227 100 100     9337 if ( $result->{phase} eq 'test'
596             && $line =~ m{open3: IO::Pipe: Can't spawn.*?TAP/Parser/Iterator/Process.pm}
597             ) {
598 2         9 $harness_error++;
599 2         6 last;
600             }
601 3225 50 66     21750 if( $line =~ /(?
      100        
      66        
      66        
      33        
602             #?
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         61 $version_error++;
610 8         40 last;
611             }
612 3217 100       13119 if ( $line =~ /No support for OS|OS unsupported/ims ) {
613 6         43 $unsupported++;
614 6         32 last;
615             }
616             }
617              
618             # if the test harness had an error, discard the report
619 127 100 100     5881 if ( $harness_error ) {
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
      100        
620 2         10 $grade = 'discard';
621 2         7 $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         66 $grade = 'na';
626 12         66 $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         54 $grade = 'na';
631 6         51 $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         13 $grade = 'unknown';
640 1         24 $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         88 $grade = 'discard';
645 14         80 $msg = "Prerequisite missing:\n$result->{prereq_pm}";
646             }
647             elsif ( $grade ne 'pass' && $result->{prereq_pm} =~ m{^\s+!}ims ) {
648 8         79 $grade = 'discard';
649 8         50 $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         30 $grade = 'discard';
657 2         13 $msg = 'No Makefile or Build file found';
658             }
659             elsif ( $result->{command} =~ /Build.*?-j/ ) {
660 2         15 $grade = 'discard';
661 2         9 $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         170 grep { /Makefile out-of-date with respect to Makefile.PL/ } @$output
666             ) {
667 1         15 $grade = 'discard';
668 1         5 $msg = 'Makefile out-of-date';
669             }
670              
671             # store results
672 127         3162 $result->{grade} = $grade;
673 127         460 $result->{grade_msg} = $msg;
674              
675 127         540 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   1137 my $result = shift;
686 354 100       7135 return if $result->{expanded}++; # only do this once
687 167         1727 $result->{prereq_pm} = _prereq_report( $result->{dist} );
688             {
689             # mirror PERL5OPT as in record_command
690 167 100       499 local $ENV{PERL5OPT} = _get_perl5opt() if _is_PL($result->{command});
  167         1955  
691 167         1361 $result->{env_vars} = _env_report();
692             }
693 167         1478 $result->{special_vars} = _special_vars_report();
694 167         1248 $result->{toolchain_versions} = _toolchain_report( $result );
695 167         3302 $result->{perl_version} = CPAN::Reporter::History::_format_perl_version();
696 167         1359 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             CCFLAGS
710             COMSPEC
711             INCLUDE
712             INSTALL_BASE
713             LANG
714             LANGUAGE
715             LD_LIBRARY_PATH
716             LDFLAGS
717             LIB
718             NON_INTERACTIVE
719             NUMBER_OF_PROCESSORS
720             PATH
721             PREFIX
722             PROCESSOR_IDENTIFIER
723             SHELL
724             TERM
725             TEMP
726             TMPDIR
727             );
728              
729             sub _env_report {
730 176     176   51255 my @vars_found;
731 176         1358 for my $var ( @env_vars ) {
732 3872 100       22099 if ( $var =~ m{^/(.+)/$} ) {
733 704         8642 push @vars_found, grep { /$1/ } keys %ENV;
  19692         78863  
734             }
735             else {
736 3168 100       18031 push @vars_found, $var if exists $ENV{$var};
737             }
738             }
739              
740 176         667 my $report = "";
741 176         1465 for my $var ( sort @vars_found ) {
742 2459         6163 my $value = $ENV{$var};
743 2459 50       4554 $value = '[undef]' if ! defined $value;
744 2459         5020 $report .= " $var = $value\n";
745             }
746 176         4425 return $report;
747             }
748              
749             #--------------------------------------------------------------------------#
750             # _file_copy_quiet
751             #
752             # manual file copy -- quietly return undef on failure
753             #--------------------------------------------------------------------------#
754              
755             sub _file_copy_quiet {
756 33     33   126 my ($source, $target) = @_;
757             # ensure we have a target directory
758 33 50       10894 mkpath( dirname($target) ) or return;
759             # read source
760 33         460 local *FH;
761 33 50       2011 open FH, "<$source" or return; ## no critic
762 33         226 my $pm_guts = do { local $/; };
  33         200  
  33         1389  
763 33         481 close FH;
764             # write target
765 33 50       6903 open FH, ">$target" or return; ## no critic
766 33         259 print FH $pm_guts;
767 33         1901 close FH;
768 33         370 return 1;
769             }
770              
771             #--------------------------------------------------------------------------#
772             # _get_perl5opt
773             #--------------------------------------------------------------------------#
774              
775             sub _get_perl5opt {
776 76   100 76   626 my $perl5opt = $ENV{PERL5OPT} || q{};
777 76 50       293 if ( $Autoflush_Lib ) {
778 76 100       495 $perl5opt .= q{ } if length $perl5opt;
779 76 50       410 $perl5opt .= "-I$Autoflush_Lib " if $] >= 5.008;
780 76         264 $perl5opt .= "-MDevel::Autoflush";
781             }
782 76         1443 return $perl5opt;
783             }
784              
785             #--------------------------------------------------------------------------#
786             # _has_recursive_make
787             #
788             # Ignore Makefile.PL in t directories
789             #--------------------------------------------------------------------------#
790              
791             sub _has_recursive_make {
792 67     67   522 my $PL_count = 0;
793             File::Find::find(
794             sub {
795 2318 100   2318   201077 if ( $_ eq 't' ) {
    100          
796 63         683 $File::Find::prune = 1;
797             }
798             elsif ( $_ eq 'Makefile.PL' ) {
799 73         3942 $PL_count++;
800             }
801             },
802 67         18124 File::Spec->curdir()
803             );
804 67         887 return $PL_count > 1;
805             }
806              
807             #--------------------------------------------------------------------------#
808             # _has_test_target
809             #--------------------------------------------------------------------------#
810              
811             sub _has_test_target {
812 47 50   47   1389 my $fh = IO::File->new("Makefile") or return;
813 47         40519 return scalar grep { /^test[ ]*:/ } <$fh>;
  42494         80299  
814             }
815              
816             #--------------------------------------------------------------------------#
817             # _init_result -- create and return a hash of values for use in
818             # report evaluation and dispatch
819             #
820             # takes same argument format as grade_*()
821             #--------------------------------------------------------------------------#
822              
823             sub _init_result {
824 173     173   22382 my ($phase, $dist, $system_command, $output, $exit_value) = @_;
825              
826 173 50 33     2284 unless ( defined $output && defined $exit_value ) {
827 0         0 my $missing;
828 0 0 0     0 if ( ! defined $output && ! defined $exit_value ) {
    0 0        
829 0         0 $missing = "exit value and output"
830             }
831             elsif ( defined $output && !defined $exit_value ) {
832 0         0 $missing = "exit value"
833             }
834             else {
835 0         0 $missing = "output";
836             }
837 0         0 $CPAN::Frontend->mywarn(
838             "CPAN::Reporter: had errors capturing $missing. Tests abandoned"
839             );
840 0         0 return;
841             }
842              
843 173 100       6762 if ( $dist->pretty_id =~ m{\w+/Perl6/} ) {
844 3         76 $CPAN::Frontend->mywarn(
845             "CPAN::Reporter: Won't report a Perl6 distribution."
846             );
847 3         188 return;
848             }
849              
850 170 100       5506 my $result = {
851             phase => $phase,
852             dist => $dist,
853             command => $system_command,
854             is_make => _is_make( $system_command ),
855             output => ref $output eq 'ARRAY' ? $output : [ split /\n/, $output ],
856             exit_value => $exit_value,
857             # Note: pretty_id is like "DAGOLDEN/CPAN-Reporter-0.40.tar.gz"
858             dist_basename => basename($dist->pretty_id),
859             dist_name => $dist->base_id,
860             };
861              
862             # Used in messages to user
863 170 100       42002 $result->{PL_file} = $result->{is_make} ? "Makefile.PL" : "Build.PL";
864 170 100       6271 $result->{make_cmd} = $result->{is_make} ? $Config{make} : "Build";
865              
866             # CPAN might fail to find an author object for some strange dists
867 170         1433 my $author = $dist->author;
868 170 50       2958 $result->{author} = defined $author ? $author->fullname : "Author";
869 170 50       3132 $result->{author_id} = defined $author ? $author->id : "" ;
870              
871 170         2424 return $result;
872             }
873              
874             #--------------------------------------------------------------------------#
875             # _is_make
876             #--------------------------------------------------------------------------#
877              
878             sub _is_make {
879 190     190   22669 my $command = shift;
880 190 100       4401 return $command =~ m{\b(?:\S*make|Makefile.PL)\b}ims ? 1 : 0;
881             }
882              
883             #--------------------------------------------------------------------------#
884             # _is_PL
885             #--------------------------------------------------------------------------#
886              
887             sub _is_PL {
888 353     353   11520 my $command = shift;
889 353 100       4500 return $command =~ m{\b(?:Makefile|Build)\.PL\b}ims ? 1 : 0;
890             }
891              
892             #--------------------------------------------------------------------------#
893             # _max_length
894             #--------------------------------------------------------------------------#
895              
896             sub _max_length {
897 352     352   3260 my ($first, @rest) = @_;
898 352         854 my $max = length $first;
899 352         1408 for my $term ( @rest ) {
900 7040 100       31534 $max = length $term if length $term > $max;
901             }
902 352         1713 return $max;
903             }
904              
905              
906             #--------------------------------------------------------------------------#
907             # _parse_tap_harness
908             #
909             # As of Test::Harness 2.99_02, the final line is provided by TAP::Harness
910             # as "Result: STATUS" where STATUS is "PASS", "FAIL" or "NOTESTS"
911             #--------------------------------------------------------------------------#
912              
913              
914             sub _parse_tap_harness {
915 252     252   744 my ($line) = @_;
916 252 100       1890 if ( $line =~ m{^Result:\s+([A-Z]+)} ) {
    50          
    100          
917 74 100       606 if ( $1 eq 'PASS' ) {
    100          
    50          
918 20         173 return ('pass', 'All tests successful');
919             }
920             elsif ( $1 eq 'FAIL' ) {
921 51         439 return ('fail', 'One or more tests failed');
922             }
923             elsif ( $1 eq 'NOTESTS' ) {
924 3         39 return ('unknown', 'No tests were run');
925             }
926             }
927             elsif ( $line =~ m{Bailout called\.\s+Further testing stopped}ms ) {
928 0         0 return ( 'fail', 'Bailed out of tests');
929             }
930             elsif ( $line =~ m{FAILED--Further testing stopped}ms ) { # TAP::Harness 3.49+
931 2         17 return ( 'fail', 'Bailed out of tests');
932             }
933 176         501 return;
934             }
935              
936             #--------------------------------------------------------------------------#
937             # _parse_test_harness
938             #
939             # Output strings taken from Test::Harness::
940             # _show_results() -- for versions < 2.57_03
941             # get_results() -- for versions >= 2.57_03
942             #--------------------------------------------------------------------------#
943              
944             sub _parse_test_harness {
945 0     0   0 my ($line) = @_;
946 0 0       0 if ( $line =~ m{^All tests successful}ms ) {
    0          
    0          
    0          
    0          
947 0         0 return ( 'pass', 'All tests successful' );
948             }
949             elsif ( $line =~ m{^FAILED--no tests were run}ms ) {
950 0         0 return ( 'unknown', 'No tests were run' );
951             }
952             elsif ( $line =~ m{^FAILED--.*--no output}ms ) {
953 0         0 return ( 'unknown', 'No tests were run');
954             }
955             elsif ( $line =~ m{FAILED--Further testing stopped}ms ) {
956 0         0 return ( 'fail', 'Bailed out of tests');
957             }
958             elsif ( $line =~ m{^Failed }ms ) { # must be lowercase
959 0         0 return ( 'fail', 'One or more tests failed');
960             }
961             else {
962 0         0 return;
963             }
964             }
965              
966             #--------------------------------------------------------------------------#
967             # _prereq_report
968             #--------------------------------------------------------------------------#
969              
970             my @prereq_sections = qw(
971             requires build_requires configure_requires opt_requires opt_build_requires
972             );
973              
974             sub _prereq_report {
975 179     179   234632 my $dist = shift;
976 179         509 my (%need, %have, %prereq_met, $report);
977              
978             # Extract requires/build_requires from CPAN dist
979 179         1297 my $prereq_pm = $dist->prereq_pm;
980              
981 179 50       2272 if ( ref $prereq_pm eq 'HASH' ) {
982             # CPAN 1.94 returns hash with requires/build_requires # so no need to support old style
983 179         1249 foreach (values %$prereq_pm) {
984 711 50 66     4150 if (defined && ref ne 'HASH') {
985 0         0 require Data::Dumper;
986 0         0 warn "Data error detecting prerequisites. Please report it to CPAN::Reporter bug tracker:";
987 0         0 warn Data::Dumper::Dumper($prereq_pm);
988 0         0 die "Stopping";
989             }
990             }
991              
992 179         1388 for my $sec ( @prereq_sections ) {
993 895 100       1357 $need{$sec} = $prereq_pm->{$sec} if keys %{ $prereq_pm->{$sec} };
  895         5607  
994             }
995             }
996              
997             # Extract configure_requires from META.yml if it exists
998 179 100 66     2916 if ( $dist->{build_dir} && -d $dist->{build_dir} ) {
999 72         2063 my $meta_yml = File::Spec->catfile($dist->{build_dir}, 'META.yml');
1000 72 100       1885 if ( -f $meta_yml ) {
1001 4         23 my @yaml = eval { Parse::CPAN::Meta::LoadFile($meta_yml) };
  4         84  
1002 4 100       17792 if ( $@ ) {
1003 2         64 $CPAN::Frontend->mywarn(
1004             "CPAN::Reporter: error parsing META.yml\n"
1005             );
1006             }
1007 4 100 66     172 if ( ref $yaml[0] eq 'HASH' &&
1008             ref $yaml[0]{configure_requires} eq 'HASH'
1009             ) {
1010 2         24 $need{configure_requires} = $yaml[0]{configure_requires};
1011             }
1012             }
1013             }
1014              
1015             # see what prereqs are satisfied in subprocess
1016 179         601 for my $section ( @prereq_sections ) {
1017 895 100       3533 next unless ref $need{$section} eq 'HASH';
1018 137         322 my @prereq_list = %{ $need{$section} };
  137         1081  
1019 137 50       603 next unless @prereq_list;
1020 137         2553 my $prereq_results = _version_finder( @prereq_list );
1021 137         521 for my $mod ( keys %{$prereq_results} ) {
  137         1001  
1022 185         1534 $have{$section}{$mod} = $prereq_results->{$mod}{have};
1023 185         1838 $prereq_met{$section}{$mod} = $prereq_results->{$mod}{met};
1024             }
1025             }
1026              
1027             # find formatting widths
1028 179         818 my ($name_width, $need_width, $have_width) = (6, 4, 4);
1029 179         615 for my $section ( @prereq_sections ) {
1030 895         1392 for my $module ( keys %{ $need{$section} } ) {
  895         4857  
1031 185         509 my $name_length = length $module;
1032 185         793 my $need_length = length $need{$section}{$module};
1033 185         509 my $have_length = length $have{$section}{$module};
1034 185 100       673 $name_width = $name_length if $name_length > $name_width;
1035 185 100       652 $need_width = $need_length if $need_length > $need_width;
1036 185 100       4145 $have_width = $have_length if $have_length > $have_width;
1037             }
1038             }
1039              
1040 179         1244 my $format_str =
1041             " \%1s \%-${name_width}s \%-${need_width}s \%-${have_width}s\n";
1042              
1043             # generate the report
1044 179         651 for my $section ( @prereq_sections ) {
1045 895 100       2506 if ( keys %{ $need{$section} } ) {
  895         4446  
1046 137         519 $report .= "$section:\n\n";
1047 137         1127 $report .= sprintf( $format_str, " ", qw/Module Need Have/ );
1048 137         1061 $report .= sprintf( $format_str, " ",
1049             "-" x $name_width,
1050             "-" x $need_width,
1051             "-" x $have_width );
1052 137         365 for my $module (sort {lc $a cmp lc $b} keys %{ $need{$section} } ) {
  137         264  
  137         685  
1053 185         623 my $need = $need{$section}{$module};
1054 185         580 my $have = $have{$section}{$module};
1055 185 100       770 my $bad = $prereq_met{$section}{$module} ? " " : "!";
1056 185         1121 $report .=
1057             sprintf( $format_str, $bad, $module, $need, $have);
1058             }
1059 137         491 $report .= "\n";
1060             }
1061             }
1062              
1063 179   100     3665 return $report || " No requirements found\n";
1064             }
1065              
1066             #--------------------------------------------------------------------------#
1067             # _print_grade_msg -
1068             #--------------------------------------------------------------------------#
1069              
1070             sub _print_grade_msg {
1071 138     138   1009 my ($phase, $result) = @_;
1072 138         570 my ($grade, $msg) = ($result->{grade}, $result->{grade_msg});
1073 138         7622 $CPAN::Frontend->myprint( "CPAN::Reporter: $phase result is '$grade'");
1074 138 50 33     11949 $CPAN::Frontend->myprint(", $msg") if defined $msg && length $msg;
1075 138         3478 $CPAN::Frontend->myprint(".\n");
1076 138         2245 return;
1077             }
1078              
1079             #--------------------------------------------------------------------------#
1080             # _prompt
1081             #
1082             # Note: always returns lowercase
1083             #--------------------------------------------------------------------------#
1084              
1085             sub _prompt {
1086 328     328   62766 my ($config, $option, $grade, $extra) = @_;
1087 328   50     2431 $extra ||= q{};
1088              
1089 328         1104 my %spec = CPAN::Reporter::Config::_config_spec();
1090              
1091             my $dispatch = CPAN::Reporter::Config::_validate_grade_action_pair(
1092 328   50     2563 $option, join(q{ }, "default:no", $config->{$option} || '')
1093             );
1094 328   66     2148 my $action = $dispatch->{$grade} || $dispatch->{default};
1095 328         1203 my $intro = $spec{$option}{prompt} . $extra . " (yes/no)";
1096 328         510 my $prompt;
1097 328 100       1896 if ( $action =~ m{^ask/yes}i ) {
    100          
1098 10         72 $prompt = CPAN::Shell::colorable_makemaker_prompt( $intro, "yes" );
1099             }
1100             elsif ( $action =~ m{^ask(/no)?}i ) {
1101 72         722 $prompt = CPAN::Shell::colorable_makemaker_prompt( $intro, "no" );
1102             }
1103             else {
1104 246         519 $prompt = $action;
1105             }
1106 328         15059 return lc $prompt;
1107             }
1108              
1109             #--------------------------------------------------------------------------#
1110             # _report_text
1111             #--------------------------------------------------------------------------#
1112              
1113             my %intro_para = (
1114             'pass' => <<'HERE',
1115             Thank you for uploading your work to CPAN. Congratulations!
1116             All tests were successful.
1117             HERE
1118              
1119             'fail' => <<'HERE',
1120             Thank you for uploading your work to CPAN. However, there was a problem
1121             testing your distribution.
1122              
1123             If you think this report is invalid, please consult the CPAN Testers Wiki
1124             for suggestions on how to avoid getting FAIL reports for missing library
1125             or binary dependencies, unsupported operating systems, and so on:
1126              
1127             http://wiki.cpantesters.org/wiki/CPANAuthorNotes
1128             HERE
1129              
1130             'unknown' => <<'HERE',
1131             Thank you for uploading your work to CPAN. However, attempting to
1132             test your distribution gave an inconclusive result.
1133              
1134             This could be because your distribution had an error during the make/build
1135             stage, did not define tests, tests could not be found, because your tests were
1136             interrupted before they finished, or because the results of the tests could not
1137             be parsed. You may wish to consult the CPAN Testers Wiki:
1138              
1139             http://wiki.cpantesters.org/wiki/CPANAuthorNotes
1140             HERE
1141              
1142             'na' => <<'HERE',
1143             Thank you for uploading your work to CPAN. While attempting to build or test
1144             this distribution, the distribution signaled that support is not available
1145             either for this operating system or this version of Perl. Nevertheless, any
1146             diagnostic output produced is provided below for reference. If this is not
1147             what you expect, you may wish to consult the CPAN Testers Wiki:
1148              
1149             http://wiki.cpantesters.org/wiki/CPANAuthorNotes
1150             HERE
1151              
1152             );
1153              
1154             sub _comment_text {
1155              
1156             # We assemble the completed comment as a series of "parts" which
1157             # will get joined together
1158 117     117   1019 my @comment_parts;
1159              
1160             # All automated testing gets a preamble
1161 117 100       563 if ($ENV{AUTOMATED_TESTING}) {
1162 111         6920 push @comment_parts,
1163             "this report is from an automated smoke testing program\n"
1164             . "and was not reviewed by a human for accuracy"
1165             }
1166              
1167             # If a comment file is provided, read it and add it to the comment
1168 117         443 my $confdir = CPAN::Reporter::Config::_get_config_dir();
1169 117         1020 my $comment_file = File::Spec->catfile($confdir, 'comment.txt');
1170 117 100 66     4547 if ( -d $confdir && -f $comment_file && -r $comment_file ) {
      66        
1171 2 50   1   174 open my $fh, '<:encoding(UTF-8)', $comment_file or die($!);
  1         20  
  1         4  
  1         16  
1172 2         2929 my $text;
1173 2         5 do {
1174 2         14 local $/ = undef; # No record (line) seperator on input
1175 2 50       132 defined( $text = <$fh> ) or die($!);
1176             };
1177 2         49 chomp($text);
1178 2         8 push @comment_parts, $text;
1179 2         52 close $fh;
1180             }
1181              
1182             # If we have an empty comment so far, add a default value
1183 117 100       601 if (scalar(@comment_parts) == 0) {
1184 5         22 push @comment_parts, 'none provided';
1185             }
1186              
1187             # Join the parts seperated by a blank line
1188 117         917 return join "\n\n", @comment_parts;
1189             }
1190              
1191             sub _report_text {
1192 117     117   347 my $data = shift;
1193 117         265 my $test_log = join(q{},@{$data->{output}});
  117         1279  
1194 117 50       652 if ( length $test_log > MAX_OUTPUT_LENGTH ) {
1195 0         0 my $max_k = int(MAX_OUTPUT_LENGTH/1000) . "K";
1196 0         0 $test_log = substr( $test_log, 0, MAX_OUTPUT_LENGTH/2 ) . "\n\n"
1197             . "[Output truncated because it exceeded $max_k]\n\n"
1198             . substr( $test_log, -(MAX_OUTPUT_LENGTH/2) );
1199             }
1200              
1201 117         480 my $comment_body = _comment_text();
1202              
1203             # generate report
1204 117         3769 my $output = << "ENDREPORT";
1205             Dear $data->{author},
1206              
1207             This is a computer-generated report for $data->{dist_name}
1208             on perl $data->{perl_version}, created by CPAN-Reporter-$CPAN::Reporter::VERSION\.
1209              
1210             $intro_para{ $data->{grade} }
1211             Sections of this report:
1212              
1213             * Tester comments
1214             * Program output
1215             * Prerequisites
1216             * Environment and other context
1217              
1218             ------------------------------
1219             TESTER COMMENTS
1220             ------------------------------
1221              
1222             Additional comments from tester:
1223              
1224             $comment_body
1225              
1226             ------------------------------
1227             PROGRAM OUTPUT
1228             ------------------------------
1229              
1230             Output from '$data->{command}':
1231              
1232             $test_log
1233             ------------------------------
1234             PREREQUISITES
1235             ------------------------------
1236              
1237             Prerequisite modules loaded:
1238              
1239             $data->{prereq_pm}
1240             ------------------------------
1241             ENVIRONMENT AND OTHER CONTEXT
1242             ------------------------------
1243              
1244             Environment variables:
1245              
1246             $data->{env_vars}
1247             Perl special variables (and OS-specific diagnostics, for MSWin32):
1248              
1249             $data->{special_vars}
1250             Perl module toolchain versions installed:
1251              
1252             $data->{toolchain_versions}
1253             ENDREPORT
1254              
1255 117         765 return $output;
1256             }
1257              
1258             #--------------------------------------------------------------------------#
1259             # _special_vars_report
1260             #--------------------------------------------------------------------------#
1261              
1262             sub _special_vars_report {
1263 176     176   36915 my $special_vars = << "HERE";
1264             \$^X = $^X
1265             \$UID/\$EUID = $< / $>
1266             \$GID = $(
1267             \$EGID = $)
1268             HERE
1269 176 50 33     1182 if ( $^O eq 'MSWin32' && eval "require Win32" ) { ## no critic
1270 0         0 my @getosversion = Win32::GetOSVersion();
1271 0         0 my $getosversion = join(", ", @getosversion);
1272 0         0 $special_vars .= " Win32::GetOSName = " . Win32::GetOSName() . "\n";
1273 0         0 $special_vars .= " Win32::GetOSVersion = $getosversion\n";
1274 0         0 $special_vars .= " Win32::FsType = " . Win32::FsType() . "\n";
1275 0         0 $special_vars .= " Win32::IsAdminUser = " . Win32::IsAdminUser() . "\n";
1276             }
1277 176         1212 return $special_vars;
1278             }
1279              
1280             #--------------------------------------------------------------------------#
1281             # _split_redirect
1282             #--------------------------------------------------------------------------#
1283              
1284             sub _split_redirect {
1285 186     186   566 my $command = shift;
1286 186         1195 my ($cmd, $prefix) = ($command =~ m{\A(.+?)(\|.*)\z});
1287 186 100       660 if (defined $cmd) {
1288 1         16 return ($cmd, $prefix);
1289             }
1290             else { # didn't match a redirection
1291 185         765 return $command
1292             }
1293             }
1294              
1295             #--------------------------------------------------------------------------#
1296             # _temp_filename -- stand-in for File::Temp for backwards compatibility
1297             #
1298             # takes an optional prefix, adds 8 random chars and returns
1299             # an absolute pathname
1300             #
1301             # NOTE -- manual unlink required
1302             #--------------------------------------------------------------------------#
1303              
1304             # @CHARS from File::Temp
1305             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
1306             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
1307             0 1 2 3 4 5 6 7 8 9 _
1308             /);
1309              
1310             sub _temp_filename {
1311 500     500   2314 my ($prefix) = @_;
1312 500 50       1749 $prefix = q{} unless defined $prefix;
1313 500         11920 $prefix .= $CHARS[ int( rand(@CHARS) ) ] for 0 .. 7;
1314 500         21896 return File::Spec->catfile(File::Spec->tmpdir(), $prefix);
1315             }
1316              
1317             #--------------------------------------------------------------------------#
1318             # _timeout_wrapper
1319             # Timeout technique adapted from App::cpanminus (thank you Miyagawa!)
1320             #--------------------------------------------------------------------------#
1321              
1322             sub _timeout_wrapper {
1323 15     15   49 my ($cmd, $timeout) = @_;
1324              
1325             # protect shell quotes
1326 15         48 $cmd = quotemeta($cmd);
1327              
1328 15         112 my $wrapper = sprintf << 'HERE', $timeout, $cmd, $cmd;
1329             use strict;
1330             my ($pid, $exitcode);
1331             eval {
1332             $pid = fork;
1333             if ($pid) {
1334             local $SIG{CHLD};
1335             local $SIG{ALRM} = sub {die 'Timeout'};
1336             alarm %s;
1337             my $wstat = waitpid $pid, 0;
1338             alarm 0;
1339             $exitcode = $wstat == -1 ? -1 : $?;
1340             } elsif ( $pid == 0 ) {
1341             setpgrp(0,0); # new process group
1342             exec "%s";
1343             }
1344             else {
1345             die "Cannot fork: $!\n" unless defined $pid;
1346             }
1347             };
1348             if ($pid && $@ =~ /Timeout/){
1349             kill -9 => $pid; # and send to our child's whole process group
1350             waitpid $pid, 0;
1351             $exitcode = 9; # force result to look like SIGKILL
1352             }
1353             elsif ($@) {
1354             die $@;
1355             }
1356             print "(%s exited with $exitcode)\n";
1357             HERE
1358 15         95 return $wrapper;
1359             }
1360              
1361             #--------------------------------------------------------------------------#
1362             # _timeout_wrapper_win32
1363             #--------------------------------------------------------------------------#
1364              
1365             sub _timeout_wrapper_win32 {
1366 0     0   0 my ($cmd, $timeout) = @_;
1367              
1368 0   0     0 $timeout ||= 0; # just in case upstream doesn't guarantee it
1369              
1370 0         0 eval "use Win32::Job ();";
1371 0 0       0 if ($@) {
1372 0         0 $CPAN::Frontend->mywarn( << 'HERE' );
1373             CPAN::Reporter: you need Win32::Job for inactivity_timeout support.
1374             Continuing without timeout...
1375             HERE
1376 0         0 return;
1377             }
1378              
1379 0         0 my ($program) = split " ", $cmd;
1380 0 0       0 if (! File::Spec->file_name_is_absolute( $program ) ) {
1381 0         0 my $exe = $program . ".exe";
1382 0         0 my ($path) = grep { -e File::Spec->catfile($_,$exe) }
1383 0         0 split /$Config{path_sep}/, $ENV{PATH};
1384 0 0       0 if (! $path) {
1385 0         0 $CPAN::Frontend->mywarn( << "HERE" );
1386             CPAN::Reporter: can't locate $exe in the PATH.
1387             Continuing without timeout...
1388             HERE
1389 0         0 return;
1390             }
1391 0         0 $program = File::Spec->catfile($path,$exe);
1392             }
1393              
1394             # protect shell quotes and other things
1395 0         0 $_ = quotemeta($_) for ($program, $cmd);
1396              
1397 0         0 my $wrapper = sprintf << 'HERE', $program, $cmd, $timeout;
1398             use strict;
1399             use Win32::Job;
1400             my $executable = "%s";
1401             my $cmd_line = "%s";
1402             my $timeout = %s;
1403              
1404             my $job = Win32::Job->new() or die $^E;
1405             my $ppid = $job->spawn($executable, $cmd_line);
1406             $job->run($timeout);
1407             my $status = $job->status;
1408             my $exitcode = $status->{$ppid}{exitcode};
1409             if ( $exitcode == 293 ) {
1410             $exitcode = 9; # map Win32::Job kill (293) to SIGKILL (9)
1411             }
1412             elsif ( $exitcode & 255 ) {
1413             $exitcode = $exitcode << 8; # how perl expects it
1414             }
1415             print "($cmd_line exited with $exitcode)\n";
1416             HERE
1417 0         0 return $wrapper;
1418             }
1419              
1420             #--------------------------------------------------------------------------#-
1421             # _toolchain_report
1422             #--------------------------------------------------------------------------#
1423              
1424             my @toolchain_mods= qw(
1425             CPAN
1426             CPAN::Meta
1427             Cwd
1428             ExtUtils::CBuilder
1429             ExtUtils::Command
1430             ExtUtils::Install
1431             ExtUtils::MakeMaker
1432             ExtUtils::Manifest
1433             ExtUtils::ParseXS
1434             File::Spec
1435             JSON
1436             JSON::PP
1437             Module::Build
1438             Module::Signature
1439             Parse::CPAN::Meta
1440             Test::Harness
1441             Test::More
1442             Test2
1443             YAML
1444             YAML::Syck
1445             version
1446             );
1447              
1448             sub _toolchain_report {
1449 176     176   3130 my ($result) = @_;
1450              
1451 176         680 my $installed = _version_finder( map { $_ => 0 } @toolchain_mods );
  3696         12701  
1452 176         5188 $result->{toolchain} = $installed;
1453              
1454 176         3081 my $mod_width = _max_length( keys %$installed );
1455             my $ver_width = _max_length(
1456 176         1355 map { $installed->{$_}{have} } keys %$installed
  3696         7726  
1457             );
1458              
1459 176         1675 my $format = " \%-${mod_width}s \%-${ver_width}s\n";
1460              
1461 176         784 my $report = "";
1462 176         1178 $report .= sprintf( $format, "Module", "Have" );
1463 176         1275 $report .= sprintf( $format, "-" x $mod_width, "-" x $ver_width );
1464              
1465 176         2749 for my $var ( sort keys %$installed ) {
1466             $report .= sprintf(" \%-${mod_width}s \%-${ver_width}s\n",
1467 3696         12316 $var, $installed->{$var}{have} );
1468             }
1469              
1470 176         2824 return $report;
1471             }
1472              
1473              
1474             #--------------------------------------------------------------------------#
1475             # _validate_metabase_args
1476             #
1477             # This is a kludge to make metabase transport args a little less
1478             # clunky for novice users
1479             #--------------------------------------------------------------------------#
1480              
1481             sub _validate_metabase_args {
1482 116     116   529 my @transport_args = @_;
1483 116         271 shift @transport_args; # drop leading 'Metabase'
1484 116         304 my (%args, $error);
1485              
1486 116 50       510 if ( @transport_args % 2 != 0 ) {
1487 0         0 $error = << "TRANSPORT_ARGS";
1488              
1489             CPAN::Reporter: Metabase 'transport' option had odd number of
1490             parameters in the config file. See documentation for proper
1491             configuration format.
1492              
1493             TRANSPORT_ARGS
1494             }
1495             else {
1496 116         1097 %args = @transport_args;
1497              
1498 116         642 for my $key ( qw/uri id_file/ ) {
1499 232 50       785 if ( ! $args{$key} ) {
1500 0         0 $error = << "TRANSPORT_ARGS";
1501              
1502             CPAN::Reporter: Metabase 'transport' option did not have
1503             a '$key' parameter in the config file. See documentation for
1504             proper configuration format.
1505              
1506             TRANSPORT_ARGS
1507             }
1508             }
1509             }
1510              
1511 116 50       3669 if ( $error ) {
1512 0         0 $CPAN::Frontend->mywarn( $error );
1513 0         0 return;
1514             }
1515              
1516 116         529 $args{id_file} = CPAN::Reporter::Config::_normalize_id_file( $args{id_file} );
1517              
1518 116 50       4427 if ( ! -r $args{id_file} ) {
1519 0         0 $CPAN::Frontend->mywarn( <<"TRANSPORT_ARGS" );
1520              
1521             CPAN::Reporter: Could not find Metabase transport 'id_file' parameter
1522             located at '$args{id_file}'.
1523             See documentation for proper configuration of the 'transport' setting.
1524              
1525             TRANSPORT_ARGS
1526 0         0 return;
1527             }
1528              
1529 116         1440 return ('Metabase', %args);
1530             }
1531              
1532              
1533             #--------------------------------------------------------------------------#
1534             # _version_finder
1535             #
1536             # module => version pairs
1537             #
1538             # This is done via an external program to show installed versions exactly
1539             # the way they would be found when test programs are run. This means that
1540             # any updates to PERL5LIB will be reflected in the results.
1541             #
1542             # File-finding logic taken from CPAN::Module::inst_file(). Logic to
1543             # handle newer Module::Build prereq syntax is taken from
1544             # CPAN::Distribution::unsat_prereq()
1545             #
1546             #--------------------------------------------------------------------------#
1547              
1548             my $version_finder = $INC{'CPAN/Reporter/PrereqCheck.pm'};
1549              
1550             sub _version_finder {
1551 314     314   18621 my %prereqs = @_;
1552              
1553 314         13607 my $perl = Probe::Perl->find_perl_interpreter();
1554 314         14807 my @prereq_results;
1555              
1556 314         2443 my $prereq_input = _temp_filename( 'CPAN-Reporter-PI-' );
1557 314 50       11958 my $fh = IO::File->new( $prereq_input, "w" )
1558             or die "Could not create temporary '$prereq_input' for prereq analysis: $!";
1559 314         182246 $fh->print( map { "$_ $prereqs{$_}\n" } keys %prereqs );
  3895         14667  
1560 314         7633 $fh->close;
1561              
1562 314     314   55982 my $prereq_result = capture { system( $perl, $version_finder, '<', $prereq_input ) };
  314         352659692  
1563              
1564 314         645775 unlink $prereq_input;
1565              
1566 314         2879 my %result;
1567 314         5014 for my $line ( split "\n", $prereq_result ) {
1568 3895 50       8458 next unless length $line;
1569 3895         14126 my ($mod, $met, $have) = split " ", $line;
1570 3895 50 33     32983 unless ( defined($mod) && defined($met) && defined($have) ) {
      33        
1571 0         0 $CPAN::Frontend->mywarn(
1572             "Error parsing output from CPAN::Reporter::PrereqCheck:\n" .
1573             $line
1574             );
1575 0         0 next;
1576             }
1577 3895         26696 $result{$mod}{have} = $have;
1578 3895         10126 $result{$mod}{met} = $met;
1579             }
1580 314         10792 return \%result;
1581             }
1582              
1583             1;
1584              
1585             # ABSTRACT: Adds CPAN Testers reporting to CPAN.pm
1586              
1587             =pod
1588              
1589             =encoding UTF-8
1590              
1591             =head1 NAME
1592              
1593             CPAN::Reporter - Adds CPAN Testers reporting to CPAN.pm
1594              
1595             =head1 VERSION
1596              
1597             version 1.2020
1598              
1599             =head1 SYNOPSIS
1600              
1601             From the CPAN shell:
1602              
1603             cpan> install Task::CPAN::Reporter
1604             cpan> reload cpan
1605             cpan> o conf init test_report
1606              
1607             Installing L will pull in additional dependencies
1608             that new CPAN Testers will need.
1609              
1610             Advanced CPAN Testers with custom L setups
1611             may wish to install only CPAN::Reporter, which has fewer dependencies.
1612              
1613             =head1 DESCRIPTION
1614              
1615             The CPAN Testers project captures and analyzes detailed results from building
1616             and testing CPAN distributions on multiple operating systems and multiple
1617             versions of Perl. This provides valuable feedback to module authors and
1618             potential users to identify bugs or platform compatibility issues and improves
1619             the overall quality and value of CPAN.
1620              
1621             One way individuals can contribute is to send a report for each module that
1622             they test or install. CPAN::Reporter is an add-on for the CPAN.pm module to
1623             send the results of building and testing modules to the CPAN Testers project.
1624             Full support for CPAN::Reporter is available in CPAN.pm as of version 1.92.
1625              
1626             =for Pod::Coverage configure
1627             grade_PL
1628             grade_make
1629             grade_test
1630             record_command
1631             test
1632              
1633             =head1 GETTING STARTED
1634              
1635             =head2 Installation
1636              
1637             The first step in using CPAN::Reporter is to install it using whatever
1638             version of CPAN.pm is already installed. CPAN.pm will be upgraded as
1639             a dependency if necessary.
1640              
1641             cpan> install CPAN::Reporter
1642              
1643             If CPAN.pm was upgraded, it needs to be reloaded.
1644              
1645             cpan> reload cpan
1646              
1647             =head2 Configuration
1648              
1649             If upgrading from a very old version of CPAN.pm, users may be prompted to renew
1650             their configuration settings, including the 'test_report' option to enable
1651             CPAN::Reporter.
1652              
1653             If not prompted automatically, users should manually initialize CPAN::Reporter
1654             support. After enabling CPAN::Reporter, CPAN.pm will automatically continue
1655             with interactive configuration of CPAN::Reporter options.
1656              
1657             cpan> o conf init test_report
1658              
1659             Users will need to enter an email address in one of the following formats:
1660              
1661             johndoe@example.com
1662             John Doe
1663             "John Q. Public"
1664              
1665             Users that are new to CPAN::Reporter should accept the recommended values
1666             for other configuration options.
1667              
1668             Users will be prompted to create a I file that uniquely
1669             identifies their test reports. See L below for details.
1670              
1671             After completing interactive configuration, be sure to commit (save) the CPAN
1672             configuration changes.
1673              
1674             cpan> o conf commit
1675              
1676             See L for advanced configuration settings.
1677              
1678             =head3 The Metabase
1679              
1680             CPAN::Reporter sends test reports to a server known as the Metabase. This
1681             requires an active Internet connection and a profile file. To create the
1682             profile, users will need to run C<<< metabase-profile >>> from a terminal window and
1683             fill the information at the prompts. This will create a file called
1684             C<<< metabase_id.json >>> in the current directory. That file should be moved to the
1685             C<<< .cpanreporter >>> directory inside the user's home directory.
1686              
1687             Users with an existing metabase profile file (e.g. from another machine),
1688             should copy it into the C<<< .cpanreporter >>> directory instead of creating
1689             a new one. Profile files may be located outside the C<<< .cpanreporter >>>
1690             directory by following instructions in L.
1691              
1692             =head3 Default Test Comments
1693              
1694             This module puts default text into the "TESTER COMMENTS" section, typically,
1695             "none provided" if doing interactive testing, or, if doing smoke testing that
1696             sets CE$ENV{AUTOMATED_TESTING}E to a true value, "this report is from an
1697             automated smoke testing program and was not reviewed by a human for
1698             accuracy." If CECPAN::ReporterE is configured to allow editing of the
1699             report, this can be edited during submission.
1700              
1701             If you wish to override the default comment, you can create a file named
1702             CEcomment.txtE in the configuration directory (typically C<<< .cpanreporter >>>
1703             under the user's home directory), with the default comment you would
1704             like to appear.
1705              
1706             Note that if your test is an automated smoke
1707             test (CE$ENV{AUTOMATED_TESTING}E is set to a true value), the smoke
1708             test notice ("this report is from an automated smoke testing program and
1709             was not reviewed by a human for accuracy") is included along with a blank
1710             line before your CEcomment.txtE, so that it is always possible to
1711             distinguish automated tests from non-automated tests that use this
1712             module.
1713              
1714             =head2 Using CPAN::Reporter
1715              
1716             Once CPAN::Reporter is enabled and configured, test or install modules with
1717             CPAN.pm as usual.
1718              
1719             For example, to test the File::Marker module:
1720              
1721             cpan> test File::Marker
1722              
1723             If a distribution's tests fail, users will be prompted to edit the report to
1724             add additional information that might help the author understand the failure.
1725              
1726             =head1 UNDERSTANDING TEST GRADES
1727              
1728             CPAN::Reporter will assign one of the following grades to the report:
1729              
1730             =over
1731              
1732             =item *
1733              
1734             C<<< pass >>> -- distribution built and tested correctly
1735              
1736             =item *
1737              
1738             C<<< fail >>> -- distribution failed to test correctly
1739              
1740             =item *
1741              
1742             C<<< unknown >>> -- distribution failed to build, had no test suite or outcome was
1743             inconclusive
1744              
1745             =item *
1746              
1747             C<<< na >>> --- distribution is not applicable to this platform andEor
1748             version of Perl
1749              
1750             =back
1751              
1752             In returning results of the test suite to CPAN.pm, "pass" and "unknown" are
1753             considered successful attempts to "make test" or "Build test" and will not
1754             prevent installation. "fail" and "na" are considered to be failures and
1755             CPAN.pm will not install unless forced.
1756              
1757             An error from Makefile.PLEBuild.PL or makeEBuild will also be graded as
1758             "unknown" and a failure will be signaled to CPAN.pm.
1759              
1760             If prerequisites specified in C<<< Makefile.PL >>> or C<<< Build.PL >>> are not available,
1761             no report will be generated and a failure will be signaled to CPAN.pm.
1762              
1763             =head1 PRIVACY WARNING
1764              
1765             CPAN::Reporter includes information in the test report about environment
1766             variables and special Perl variables that could be affecting test results in
1767             order to help module authors interpret the results of the tests. This includes
1768             information about paths, terminal, locale, userEgroup ID, installed toolchain
1769             modules (e.g. ExtUtils::MakeMaker) and so on.
1770              
1771             These have been intentionally limited to items that should not cause harmful
1772             personal information to be revealed -- it does I include your entire
1773             environment. Nevertheless, please do not use CPAN::Reporter if you are
1774             concerned about the disclosure of this information as part of your test report.
1775              
1776             Users wishing to review this information may choose to edit the report
1777             prior to sending it.
1778              
1779             =head1 BUGS
1780              
1781             Using command_timeout on Linux may cause problems. See
1782             L
1783              
1784             Please report any bugs or feature using the CPAN Request Tracker.
1785             Bugs can be submitted through the web interface at
1786             L
1787              
1788             When submitting a bug or request, please include a test-file or a patch to an
1789             existing test-file that illustrates the bug or desired feature.
1790              
1791             =head1 SEE ALSO
1792              
1793             Information about CPAN::Testers:
1794              
1795             =over
1796              
1797             =item *
1798              
1799             L -- overview of CPAN Testers architecture stack
1800              
1801             =item *
1802              
1803             L -- project home with all reports
1804              
1805             =item *
1806              
1807             L -- documentation and wiki
1808              
1809             =back
1810              
1811             Additional Documentation:
1812              
1813             =over
1814              
1815             =item *
1816              
1817             L -- advanced configuration settings
1818              
1819             =item *
1820              
1821             L -- hints and tips
1822              
1823             =back
1824              
1825             =for :stopwords cpan testmatrix url bugtracker rt cpants kwalitee diff irc mailto metadata placeholders metacpan
1826              
1827             =head1 SUPPORT
1828              
1829             =head2 Bugs / Feature Requests
1830              
1831             Please report any bugs or feature requests through the issue tracker
1832             at L.
1833             You will be notified automatically of any progress on your issue.
1834              
1835             =head2 Source Code
1836              
1837             This is open source software. The code repository is available for
1838             public review and contribution under the terms of the license.
1839              
1840             L
1841              
1842             git clone https://github.com/cpan-testers/CPAN-Reporter.git
1843              
1844             =head1 AUTHOR
1845              
1846             David Golden
1847              
1848             =head1 CONTRIBUTORS
1849              
1850             =for stopwords Alexandr Ciornii Breno G. de Oliveira Christian Walde David Cantrell Ed J Graham Knop Håkon Hægland James E Keenan J. Maslak José Joaquín Atria Kent Fredric Leon Timmermans Matthew Musgrove Patrice Clement Reini Urban Scott Wiersdorf Slaven Rezic
1851              
1852             =over 4
1853              
1854             =item *
1855              
1856             Alexandr Ciornii
1857              
1858             =item *
1859              
1860             Breno G. de Oliveira
1861              
1862             =item *
1863              
1864             Christian Walde
1865              
1866             =item *
1867              
1868             David Cantrell
1869              
1870             =item *
1871              
1872             Ed J
1873              
1874             =item *
1875              
1876             Graham Knop
1877              
1878             =item *
1879              
1880             Håkon Hægland
1881              
1882             =item *
1883              
1884             James E Keenan
1885              
1886             =item *
1887              
1888             J. Maslak
1889              
1890             =item *
1891              
1892             José Joaquín Atria
1893              
1894             =item *
1895              
1896             Kent Fredric
1897              
1898             =item *
1899              
1900             Leon Timmermans
1901              
1902             =item *
1903              
1904             Matthew Musgrove
1905              
1906             =item *
1907              
1908             Patrice Clement
1909              
1910             =item *
1911              
1912             Reini Urban
1913              
1914             =item *
1915              
1916             Scott Wiersdorf
1917              
1918             =item *
1919              
1920             Slaven Rezic
1921              
1922             =back
1923              
1924             =head1 COPYRIGHT AND LICENSE
1925              
1926             This software is Copyright (c) 2023 by David Golden.
1927              
1928             This is free software, licensed under:
1929              
1930             The Apache License, Version 2.0, January 2004
1931              
1932             =cut
1933              
1934             __END__