1 package CPANPLUS::Internals::Report;
6 use CPANPLUS::Internals::Constants;
7 use CPANPLUS::Internals::Constants::Report;
11 use Params::Check qw[check];
12 use Module::Load::Conditional qw[can_load];
13 use Locale::Maketext::Simple Class => 'CPANPLUS', Style => 'gettext';
15 $Params::Check::VERBOSE = 1;
17 ### for the version ###
18 require CPANPLUS::Internals;
22 CPANPLUS::Internals::Report
26 ### enable test reporting
27 $cb->configure_object->set_conf( cpantest => 1 );
29 ### set custom mx host, shouldn't normally be needed
30 $cb->configure_object->set_conf( cpantest_mx => 'smtp.example.com' );
34 This module provides all the functionality to send test reports to
35 C<http://testers.cpan.org> using the C<Test::Reporter> module.
37 All methods will be called automatically if you have C<CPANPLUS>
38 configured to enable test reporting (see the C<SYNOPSIS>).
42 =head2 $bool = $cb->_have_query_report_modules
44 This function checks if all the required modules are here for querying
45 reports. It returns true and loads them if they are, or returns false
48 =head2 $bool = $cb->_have_send_report_modules
50 This function checks if all the required modules are here for sending
51 reports. It returns true and loads them if they are, or returns false
56 'File::Fetch' => '0.08',
57 'YAML::Tiny' => '0.0',
58 'File::Temp' => '0.0',
63 'Test::Reporter' => '1.34',
66 sub _have_query_report_modules {
68 my $conf = $self->configure_object;
72 verbose => { default => $conf->get_conf('verbose') },
75 my $args = check( $tmpl, \%hash ) or return;
77 return can_load( modules => $query_list, verbose => $args->{verbose} )
82 sub _have_send_report_modules {
84 my $conf = $self->configure_object;
88 verbose => { default => $conf->get_conf('verbose') },
91 my $args = check( $tmpl, \%hash ) or return;
93 return can_load( modules => $send_list, verbose => $args->{verbose} )
99 =head2 @list = $cb->_query_report( module => $modobj, [all_versions => BOOL, verbose => BOOL] )
101 This function queries the CPAN testers database at
102 I<http://testers.cpan.org/> for test results of specified module objects,
103 module names or distributions.
105 The optional argument C<all_versions> controls whether all versions of
106 a given distribution should be grabbed. It defaults to false
107 (fetching only reports for the current version).
109 Returns the a list with the following data structures (for CPANPLUS
110 version 0.042) on success, or false on failure:
114 'dist' => 'CPANPLUS-0.042',
115 'platform' => 'i686-pld-linux-thread-multi'
119 'dist' => 'CPANPLUS-0.042',
120 'platform' => 'i686-linux-thread-multi'
124 'dist' => 'CPANPLUS-0.042',
125 'platform' => 'cygwin-multi-64int',
126 'details' => 'http://nntp.x.perl.org/group/perl.cpan.testers/99371'
130 'dist' => 'CPANPLUS-0.042',
131 'platform' => 'i586-linux',
132 'details' => 'http://nntp.x.perl.org/group/perl.cpan.testers/99396'
135 The status of the test can be one of the following:
136 UNKNOWN, PASS, FAIL or NA (not applicable).
142 my $conf = $self->configure_object;
145 my($mod, $verbose, $all);
147 module => { required => 1, allow => IS_MODOBJ,
149 verbose => { default => $conf->get_conf('verbose'),
150 store => \$verbose },
151 all_versions => { default => 0, store => \$all },
154 check( $tmpl, \%hash ) or return;
156 ### check if we have the modules we need for querying
157 return unless $self->_have_query_report_modules( verbose => 1 );
160 ### XXX no longer use LWP here. However, that means we don't
161 ### automagically set proxies anymore!!!
162 # my $ua = LWP::UserAgent->new;
163 # $ua->agent( CPANPLUS_UA->() );
165 ### set proxies if we have them ###
168 my $url = TESTERS_URL->($mod->package_name);
169 my $ff = File::Fetch->new( uri => $url );
171 msg( loc("Fetching: '%1'", $url), $verbose );
174 my $tempdir = File::Temp::tempdir();
175 my $where = $ff->fetch( to => $tempdir );
178 error( loc( "Fetching report for '%1' failed: %2",
179 $url, $ff->error ) );
183 my $fh = OPEN_FILE->( $where );
185 do { local $/; <$fh> };
188 my ($aref) = eval { YAML::Tiny::Load( $res ) };
191 error(loc("Error reading result: %1", $@));
195 my $dist = $mod->package_name .'-'. $mod->package_version;
198 for my $href ( @$aref ) {
199 next unless $all or defined $href->{'distversion'} &&
200 $href->{'distversion'} eq $dist;
202 push @rv, { platform => $href->{'platform'},
203 grade => $href->{'action'},
204 dist => $href->{'distversion'},
205 ( $href->{'action'} eq 'FAIL'
206 ? (details => TESTERS_DETAILS_URL->($mod->package_name))
217 =head2 $bool = $cb->_send_report( module => $modobj, buffer => $make_output, failed => BOOL, [save => BOOL, address => $email_to, dontcc => BOOL, verbose => BOOL, force => BOOL]);
219 This function sends a testers report to C<cpan-testers@perl.org> for a
220 particular distribution.
221 It returns true on success, and false on failure.
223 It takes the following options:
229 The module object of this particular distribution
233 The output buffer from the 'make/make test' process
237 Boolean indicating if the 'make/make test' went wrong
241 Boolean indicating if the report should be saved locally instead of
242 mailed out. If provided, this function will return the location the
243 report was saved to, rather than a simple boolean 'TRUE'.
249 The email address to mail the report for. You should never need to
250 override this, but it might be useful for debugging purposes.
252 Defaults to C<cpan-testers@perl.org>.
256 Boolean indicating whether or not we should Cc: the author. If false,
257 previous error reports are inspected and checked if the author should
258 be mailed. If set to true, these tests are skipped and the author is
259 definitely not Cc:'d.
260 You should probably not change this setting.
266 Boolean indicating on whether or not to be verbose.
268 Defaults to your configuration settings
272 Boolean indicating whether to force the sending, even if the max
273 amount of reports for fails have already been reached, or if you
274 may already have sent it before.
276 Defaults to your configuration settings
284 my $conf = $self->configure_object;
287 ### do you even /have/ test::reporter? ###
288 unless( $self->_have_send_report_modules(verbose => 1) ) {
289 error( loc( "You don't have '%1' (or modules required by '%2') ".
290 "installed, you cannot report test results.",
291 'Test::Reporter', 'Test::Reporter' ) );
295 ### check arguments ###
296 my ($buffer, $failed, $mod, $verbose, $force, $address, $save, $dontcc,
299 module => { required => 1, store => \$mod, allow => IS_MODOBJ },
300 buffer => { required => 1, store => \$buffer },
301 failed => { required => 1, store => \$failed },
302 address => { default => CPAN_TESTERS_EMAIL, store => \$address },
303 save => { default => 0, store => \$save },
304 dontcc => { default => 0, store => \$dontcc },
305 verbose => { default => $conf->get_conf('verbose'),
306 store => \$verbose },
307 force => { default => $conf->get_conf('force'),
310 => { default => 0, store => \$tests_skipped },
313 check( $tmpl, \%hash ) or return;
315 ### get the data to fill the email with ###
316 my $name = $mod->module;
317 my $dist = $mod->package_name . '-' . $mod->package_version;
318 my $author = $mod->author->author;
319 my $email = $mod->author->email || CPAN_MAIL_ACCOUNT->( $author );
320 my $cp_conf = $conf->get_conf('cpantest') || '';
321 my $int_ver = $CPANPLUS::Internals::VERSION;
322 my $cb = $mod->parent;
325 ### determine the grade now ###
328 ### check if this is a platform specific module ###
329 ### if we failed the test, there may be reasons why
330 ### an 'NA' might have to be insted
331 GRADE: { if ( $failed ) {
334 ### XXX duplicated logic between this block
335 ### and REPORTED_LOADED_PREREQS :(
337 ### figure out if the prereqs are on CPAN at all
338 ### -- if not, send NA grade
339 ### Also, if our version of prereqs is too low,
340 ### -- send NA grade.
341 ### This is to address bug: #25327: do not count
342 ### as FAIL modules where prereqs are not filled
343 { my $prq = $mod->status->prereqs || {};
345 while( my($prq_name,$prq_ver) = each %$prq ) {
346 my $obj = $cb->module_tree( $prq_name );
349 msg(loc( "Prerequisite '%1' for '%2' could not be obtained".
350 " from CPAN -- sending N/A grade",
351 $prq_name, $name ), $verbose );
357 if( $cb->_vcmp( $prq_ver, $obj->installed_version ) > 0 ) {
358 msg(loc( "Installed version of '%1' ('%2') is too low for ".
359 "'%3' (needs '%4') -- sending N/A grade",
360 $prq_name, $obj->installed_version,
361 $name, $prq_ver ), $verbose );
369 unless( RELEVANT_TEST_RESULT->($mod) ) {
371 "'%1' is a platform specific module, and the test results on".
372 " your platform are not relevant --sending N/A grade.",
377 } elsif ( UNSUPPORTED_OS->( $buffer ) ) {
379 "'%1' is a platform specific module, and the test results on".
380 " your platform are not relevant --sending N/A grade.",
385 ### you dont have a high enough perl version?
386 } elsif ( PERL_VERSION_TOO_LOW->( $buffer ) ) {
387 msg(loc("'%1' requires a higher version of perl than your current ".
388 "version -- sending N/A grade.", $name), $verbose);
392 ### perhaps where were no tests...
393 ### see if the thing even had tests ###
394 } elsif ( NO_TESTS_DEFINED->( $buffer ) ) {
395 $grade = GRADE_UNKNOWN;
402 ### if we got here, it didn't fail and tests were present.. so a PASS
408 ### so an error occurred, let's see what stage it went wrong in ###
410 if( $grade eq GRADE_FAIL or $grade eq GRADE_UNKNOWN) {
412 ### return if one or more missing external libraries
413 if( my @missing = MISSING_EXTLIBS_LIST->($buffer) ) {
414 msg(loc("Not sending test report - " .
415 "external libraries not pre-installed"));
419 ### will be 'fetch', 'make', 'test', 'install', etc ###
420 my $stage = TEST_FAIL_STAGE->($buffer);
422 ### return if we're only supposed to report make_test failures ###
423 return 1 if $cp_conf =~ /\bmaketest_only\b/i
424 and ($stage !~ /\btest\b/);
427 $message = REPORT_MESSAGE_HEADER->( $int_ver, $author );
429 ### the bit where we inform what went wrong
430 $message .= REPORT_MESSAGE_FAIL_HEADER->( $stage, $buffer );
432 ### was it missing prereqs? ###
433 if( my @missing = MISSING_PREREQS_LIST->($buffer) ) {
434 if(!$self->_verify_missing_prereqs(
438 msg(loc("Not sending test report - " .
439 "bogus missing prerequisites report"));
442 $message .= REPORT_MISSING_PREREQS->($author,$email,@missing);
445 ### was it missing test files? ###
446 if( NO_TESTS_DEFINED->($buffer) ) {
447 $message .= REPORT_MISSING_TESTS->();
450 ### add a list of what modules have been loaded of your prereqs list
451 $message .= REPORT_LOADED_PREREQS->($mod);
454 $message .= REPORT_MESSAGE_FOOTER->();
456 ### it may be another grade than fail/unknown.. may be worth noting
457 ### that tests got skipped, since the buffer is not added in
458 } elsif ( $tests_skipped ) {
459 $message .= REPORT_TESTS_SKIPPED->();
462 ### if it failed, and that already got reported, we're not cc'ing the
463 ### author. Also, 'dont_cc' might be in the config, so check this;
464 my $dont_cc_author = $dontcc;
466 unless( $dont_cc_author ) {
467 if( $cp_conf =~ /\bdont_cc\b/i ) {
470 } elsif ( $grade eq GRADE_PASS ) {
473 } elsif( $grade eq GRADE_FAIL ) {
475 $self->_query_report( module => $mod, verbose => $verbose );
477 ### if we can't fetch it, we'll just assume no one
480 if( @already_sent ) {
481 for my $href (@already_sent) {
482 $count++ if uc $href->{'grade'} eq uc GRADE_FAIL;
486 if( $count > MAX_REPORT_SEND and !$force) {
487 msg(loc("'%1' already reported for '%2', ".
488 "not cc-ing the author",
489 GRADE_FAIL, $dist ), $verbose );
495 msg( loc("Sending test report for '%1'", $dist), $verbose);
497 ### reporter object ###
498 my $reporter = Test::Reporter->new(
500 distribution => $dist,
501 via => "CPANPLUS $int_ver",
502 timeout => $conf->get_conf('timeout') || 60,
503 debug => $conf->get_conf('debug'),
506 ### set a custom mx, if requested
507 $reporter->mx( [ $conf->get_conf('cpantest_mx') ] )
508 if $conf->get_conf('cpantest_mx');
510 ### set the from address ###
511 $reporter->from( $conf->get_conf('email') )
512 if $conf->get_conf('email') !~ /\@example\.\w+$/i;
514 ### give the user a chance to programattically alter the message
515 $message = $self->_callbacks->munge_test_report->($mod, $message, $grade);
517 ### add the body if we have any ###
518 $reporter->comments( $message ) if defined $message && length $message;
520 ### do a callback to ask if we should send the report
521 unless ($self->_callbacks->send_test_report->($mod, $grade)) {
522 msg(loc("Ok, not sending test report"));
526 ### do a callback to ask if we should edit the report
527 if ($self->_callbacks->edit_test_report->($mod, $grade)) {
528 ### test::reporter 1.20 and lower don't have a way to set
529 ### the preferred editor with a method call, but it does
530 ### respect your env variable, so let's set that.
531 local $ENV{VISUAL} = $conf->get_program('editor')
532 if $conf->get_program('editor');
534 $reporter->edit_comments;
537 ### people to mail ###
539 #push @inform, $email unless $dont_cc_author;
541 ### allow to be overridden, but default to the normal address ###
542 $reporter->address( $address );
544 ### should we save it locally? ###
546 if( my $file = $reporter->write() ) {
547 msg(loc("Successfully wrote report for '%1' to '%2'",
548 $dist, $file), $verbose);
552 error(loc("Failed to write report for '%1'", $dist));
556 ### should we send it to a bunch of people? ###
557 ### XXX should we do an 'already sent' check? ###
558 } elsif( $reporter->send( @inform ) ) {
559 msg(loc("Successfully sent '%1' report for '%2'", $grade, $dist),
563 ### something broke :( ###
565 error(loc("Could not send '%1' report for '%2': %3",
566 $grade, $dist, $reporter->errstr));
571 sub _verify_missing_prereqs {
575 ### check arguments ###
578 module => { required => 1, store => \$mod },
579 missing => { required => 1, store => \$missing },
582 check( $tmpl, \%hash ) or return;
585 my %missing = map {$_ => 1} @$missing;
586 my $conf = $self->configure_object;
587 my $extract = $mod->status->extract;
589 ### Read pre-requisites from Makefile.PL or Build.PL (if there is one),
592 ### 'Compress::Zlib' => '1.20',
593 ### 'Test::More' => 0,
595 ### Build.PL uses 'requires' instead of 'PREREQ_PM'.
598 push @search, ($extract ? MAKEFILE_PL->( $extract ) : MAKEFILE_PL->());
599 push @search, ($extract ? BUILD_PL->( $extract ) : BUILD_PL->());
601 for my $file ( @search ) {
602 if(-e $file and -r $file) {
603 my $slurp = $self->_get_file_contents(file => $file);
605 ($slurp =~ /'?(?:PREREQ_PM|requires)'?\s*=>\s*{(.*?)}/s);
607 ($prereq =~ /'?([\w\:]+)'?\s*=>\s*'?\d[\d\.\-\_]*'?/sg);
608 delete $missing{$_} for(@prereq);
612 return 1 if(keys %missing); # There ARE missing prerequisites
613 return; # All prerequisites accounted for
620 # c-indentation-style: bsd
622 # indent-tabs-mode: nil
624 # vim: expandtab shiftwidth=4: