1# -*- Mode: cperl; cperl-indent-level: 4 -*- 2# $Id: Straps.pm,v 1.35 2003/12/31 02:34:22 andy Exp $ 3 4package Test::Harness::Straps; 5 6use strict; 7use vars qw($VERSION); 8use Config; 9$VERSION = '0.19'; 10 11use Test::Harness::Assert; 12use Test::Harness::Iterator; 13 14# Flags used as return values from our methods. Just for internal 15# clarification. 16my $TRUE = (1==1); 17my $FALSE = !$TRUE; 18my $YES = $TRUE; 19my $NO = $FALSE; 20 21 22=head1 NAME 23 24Test::Harness::Straps - detailed analysis of test results 25 26=head1 SYNOPSIS 27 28 use Test::Harness::Straps; 29 30 my $strap = Test::Harness::Straps->new; 31 32 # Various ways to interpret a test 33 my %results = $strap->analyze($name, \@test_output); 34 my %results = $strap->analyze_fh($name, $test_filehandle); 35 my %results = $strap->analyze_file($test_file); 36 37 # UNIMPLEMENTED 38 my %total = $strap->total_results; 39 40 # Altering the behavior of the strap UNIMPLEMENTED 41 my $verbose_output = $strap->dump_verbose(); 42 $strap->dump_verbose_fh($output_filehandle); 43 44 45=head1 DESCRIPTION 46 47B<THIS IS ALPHA SOFTWARE> in that the interface is subject to change 48in incompatible ways. It is otherwise stable. 49 50Test::Harness is limited to printing out its results. This makes 51analysis of the test results difficult for anything but a human. To 52make it easier for programs to work with test results, we provide 53Test::Harness::Straps. Instead of printing the results, straps 54provide them as raw data. You can also configure how the tests are to 55be run. 56 57The interface is currently incomplete. I<Please> contact the author 58if you'd like a feature added or something change or just have 59comments. 60 61=head1 Construction 62 63=head2 C<new> 64 65 my $strap = Test::Harness::Straps->new; 66 67Initialize a new strap. 68 69=cut 70 71sub new { 72 my($proto) = shift; 73 my($class) = ref $proto || $proto; 74 75 my $self = bless {}, $class; 76 $self->_init; 77 78 return $self; 79} 80 81=head2 C<_init> 82 83 $strap->_init; 84 85Initialize the internal state of a strap to make it ready for parsing. 86 87=cut 88 89sub _init { 90 my($self) = shift; 91 92 $self->{_is_vms} = ( $^O eq 'VMS' ); 93 $self->{_is_win32} = ( $^O =~ /^(MS)?Win32$/ ); 94 $self->{_is_macos} = ( $^O eq 'MacOS' ); 95} 96 97=head1 Analysis 98 99=head2 C<analyze> 100 101 my %results = $strap->analyze($name, \@test_output); 102 103Analyzes the output of a single test, assigning it the given C<$name> 104for use in the total report. Returns the C<%results> of the test. 105See L<Results>. 106 107C<@test_output> should be the raw output from the test, including 108newlines. 109 110=cut 111 112sub analyze { 113 my($self, $name, $test_output) = @_; 114 115 my $it = Test::Harness::Iterator->new($test_output); 116 return $self->_analyze_iterator($name, $it); 117} 118 119 120sub _analyze_iterator { 121 my($self, $name, $it) = @_; 122 123 $self->_reset_file_state; 124 $self->{file} = $name; 125 my %totals = ( 126 max => 0, 127 seen => 0, 128 129 ok => 0, 130 todo => 0, 131 skip => 0, 132 bonus => 0, 133 134 details => [] 135 ); 136 137 # Set them up here so callbacks can have them. 138 $self->{totals}{$name} = \%totals; 139 while( defined(my $line = $it->next) ) { 140 $self->_analyze_line($line, \%totals); 141 last if $self->{saw_bailout}; 142 } 143 144 $totals{skip_all} = $self->{skip_all} if defined $self->{skip_all}; 145 146 my $passed = ($totals{max} == 0 && defined $totals{skip_all}) || 147 ($totals{max} && $totals{seen} && 148 $totals{max} == $totals{seen} && 149 $totals{max} == $totals{ok}); 150 $totals{passing} = $passed ? 1 : 0; 151 152 return %totals; 153} 154 155 156sub _analyze_line { 157 my($self, $line, $totals) = @_; 158 159 my %result = (); 160 161 $self->{line}++; 162 163 my $type; 164 if( $self->_is_header($line) ) { 165 $type = 'header'; 166 167 $self->{saw_header}++; 168 169 $totals->{max} += $self->{max}; 170 } 171 elsif( $self->_is_test($line, \%result) ) { 172 $type = 'test'; 173 174 $totals->{seen}++; 175 $result{number} = $self->{'next'} unless $result{number}; 176 177 # sometimes the 'not ' and the 'ok' are on different lines, 178 # happens often on VMS if you do: 179 # print "not " unless $test; 180 # print "ok $num\n"; 181 if( $self->{saw_lone_not} && 182 ($self->{lone_not_line} == $self->{line} - 1) ) 183 { 184 $result{ok} = 0; 185 } 186 187 my $pass = $result{ok}; 188 $result{type} = 'todo' if $self->{todo}{$result{number}}; 189 190 if( $result{type} eq 'todo' ) { 191 $totals->{todo}++; 192 $pass = 1; 193 $totals->{bonus}++ if $result{ok} 194 } 195 elsif( $result{type} eq 'skip' ) { 196 $totals->{skip}++; 197 $pass = 1; 198 } 199 200 $totals->{ok}++ if $pass; 201 202 if( $result{number} > 100000 && $result{number} > $self->{max} ) { 203 warn "Enormous test number seen [test $result{number}]\n"; 204 warn "Can't detailize, too big.\n"; 205 } 206 else { 207 $totals->{details}[$result{number} - 1] = 208 {$self->_detailize($pass, \%result)}; 209 } 210 211 # XXX handle counter mismatch 212 } 213 elsif ( $self->_is_bail_out($line, \$self->{bailout_reason}) ) { 214 $type = 'bailout'; 215 $self->{saw_bailout} = 1; 216 } 217 else { 218 $type = 'other'; 219 } 220 221 $self->{callback}->($self, $line, $type, $totals) if $self->{callback}; 222 223 $self->{'next'} = $result{number} + 1 if $type eq 'test'; 224} 225 226=head2 C<analyze_fh> 227 228 my %results = $strap->analyze_fh($name, $test_filehandle); 229 230Like C<analyze>, but it reads from the given filehandle. 231 232=cut 233 234sub analyze_fh { 235 my($self, $name, $fh) = @_; 236 237 my $it = Test::Harness::Iterator->new($fh); 238 $self->_analyze_iterator($name, $it); 239} 240 241=head2 C<analyze_file> 242 243 my %results = $strap->analyze_file($test_file); 244 245Like C<analyze>, but it runs the given C<$test_file> and parses its 246results. It will also use that name for the total report. 247 248=cut 249 250sub analyze_file { 251 my($self, $file) = @_; 252 253 unless( -e $file ) { 254 $self->{error} = "$file does not exist"; 255 return; 256 } 257 258 unless( -r $file ) { 259 $self->{error} = "$file is not readable"; 260 return; 261 } 262 263 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB; 264 265 # *sigh* this breaks under taint, but open -| is unportable. 266 my $line = $self->_command_line($file); 267 unless( open(FILE, "$line|") ) { 268 print "can't run $file. $!\n"; 269 return; 270 } 271 272 my %results = $self->analyze_fh($file, \*FILE); 273 my $exit = close FILE; 274 $results{'wait'} = $?; 275 if( $? && $self->{_is_vms} ) { 276 eval q{use vmsish "status"; $results{'exit'} = $?}; 277 } 278 else { 279 $results{'exit'} = _wait2exit($?); 280 } 281 $results{passing} = 0 unless $? == 0; 282 283 $self->_restore_PERL5LIB(); 284 285 return %results; 286} 287 288 289eval { require POSIX; &POSIX::WEXITSTATUS(0) }; 290if( $@ ) { 291 *_wait2exit = sub { $_[0] >> 8 }; 292} 293else { 294 *_wait2exit = sub { POSIX::WEXITSTATUS($_[0]) } 295} 296 297=head2 C<_command_line( $file )> 298 299 my $command_line = $self->_command_line(); 300 301Returns the full command line that will be run to test I<$file>. 302 303=cut 304 305sub _command_line { 306 my $self = shift; 307 my $file = shift; 308 309 my $command = $self->_command(); 310 my $switches = $self->_switches($file); 311 312 $file = qq["$file"] if ($file =~ /\s/) && ($file !~ /^".*"$/); 313 my $line = "$command $switches $file"; 314 315 return $line; 316} 317 318 319=head2 C<_command> 320 321 my $command = $self->_command(); 322 323Returns the command that runs the test. Combine this with _switches() 324to build a command line. 325 326Typically this is C<$^X>, but you can set C<$ENV{HARNESS_COMMAND}> 327to use a different Perl than what you're running the harness under. 328This might be to run a threaded Perl, for example. 329 330You can also overload this method if you've built your own strap subclass, 331such as a PHP interpreter for a PHP-based strap. 332 333=cut 334 335sub _command { 336 my $self = shift; 337 338 return $ENV{HARNESS_PERL} if defined $ENV{HARNESS_PERL}; 339 return "MCR $^X" if $self->{_is_vms}; 340 return Win32::GetShortPathName($^X) if $self->{_is_win32}; 341 return $^X; 342} 343 344 345=head2 C<_switches> 346 347 my $switches = $self->_switches($file); 348 349Formats and returns the switches necessary to run the test. 350 351=cut 352 353sub _switches { 354 my($self, $file) = @_; 355 356 my @existing_switches = $self->_cleaned_switches( $Test::Harness::Switches, $ENV{HARNESS_PERL_SWITCHES} ); 357 my @derived_switches; 358 359 local *TEST; 360 open(TEST, $file) or print "can't open $file. $!\n"; 361 my $shebang = <TEST>; 362 close(TEST) or print "can't close $file. $!\n"; 363 364 my $taint = ( $shebang =~ /^#!.*\bperl.*\s-\w*([Tt]+)/ ); 365 push( @derived_switches, "-$1" ) if $taint; 366 367 # When taint mode is on, PERL5LIB is ignored. So we need to put 368 # all that on the command line as -Is. 369 # MacPerl's putenv is broken, so it will not see PERL5LIB, tainted or not. 370 if ( $taint || $self->{_is_macos} ) { 371 my @inc = $self->_filtered_INC; 372 push @derived_switches, map { "-I$_" } @inc; 373 } 374 375 # Quote the argument if there's any whitespace in it, or if 376 # we're VMS, since VMS requires all parms quoted. Also, don't quote 377 # it if it's already quoted. 378 for ( @derived_switches ) { 379 $_ = qq["$_"] if ((/\s/ || $self->{_is_vms}) && !/^".*"$/ ); 380 } 381 return join( " ", @existing_switches, @derived_switches ); 382} 383 384=head2 C<_cleaned_switches> 385 386 my @switches = $self->_cleaned_switches( @switches_from_user ); 387 388Returns only defined, non-blank, trimmed switches from the parms passed. 389 390=cut 391 392sub _cleaned_switches { 393 my $self = shift; 394 395 local $_; 396 397 my @switches; 398 for ( @_ ) { 399 my $switch = $_; 400 next unless defined $switch; 401 $switch =~ s/^\s+//; 402 $switch =~ s/\s+$//; 403 push( @switches, $switch ) if $switch ne ""; 404 } 405 406 return @switches; 407} 408 409=head2 C<_INC2PERL5LIB> 410 411 local $ENV{PERL5LIB} = $self->_INC2PERL5LIB; 412 413Takes the current value of C<@INC> and turns it into something suitable 414for putting onto C<PERL5LIB>. 415 416=cut 417 418sub _INC2PERL5LIB { 419 my($self) = shift; 420 421 $self->{_old5lib} = $ENV{PERL5LIB}; 422 423 return join $Config{path_sep}, $self->_filtered_INC; 424} 425 426=head2 C<_filtered_INC> 427 428 my @filtered_inc = $self->_filtered_INC; 429 430Shortens C<@INC> by removing redundant and unnecessary entries. 431Necessary for OSes with limited command line lengths, like VMS. 432 433=cut 434 435sub _filtered_INC { 436 my($self, @inc) = @_; 437 @inc = @INC unless @inc; 438 439 if( $self->{_is_vms} ) { 440 # VMS has a 255-byte limit on the length of %ENV entries, so 441 # toss the ones that involve perl_root, the install location 442 @inc = grep !/perl_root/i, @inc; 443 444 } elsif ( $self->{_is_win32} ) { 445 # Lose any trailing backslashes in the Win32 paths 446 s/[\\\/+]$// foreach @inc; 447 } 448 449 my %dupes; 450 @inc = grep !$dupes{$_}++, @inc; 451 452 return @inc; 453} 454 455 456=head2 C<_restore_PERL5LIB> 457 458 $self->_restore_PERL5LIB; 459 460This restores the original value of the C<PERL5LIB> environment variable. 461Necessary on VMS, otherwise a no-op. 462 463=cut 464 465sub _restore_PERL5LIB { 466 my($self) = shift; 467 468 return unless $self->{_is_vms}; 469 470 if (defined $self->{_old5lib}) { 471 $ENV{PERL5LIB} = $self->{_old5lib}; 472 } 473} 474 475=head1 Parsing 476 477Methods for identifying what sort of line you're looking at. 478 479=head2 C<_is_comment> 480 481 my $is_comment = $strap->_is_comment($line, \$comment); 482 483Checks if the given line is a comment. If so, it will place it into 484C<$comment> (sans #). 485 486=cut 487 488sub _is_comment { 489 my($self, $line, $comment) = @_; 490 491 if( $line =~ /^\s*\#(.*)/ ) { 492 $$comment = $1; 493 return $YES; 494 } 495 else { 496 return $NO; 497 } 498} 499 500=head2 C<_is_header> 501 502 my $is_header = $strap->_is_header($line); 503 504Checks if the given line is a header (1..M) line. If so, it places how 505many tests there will be in C<< $strap->{max} >>, a list of which tests 506are todo in C<< $strap->{todo} >> and if the whole test was skipped 507C<< $strap->{skip_all} >> contains the reason. 508 509=cut 510 511# Regex for parsing a header. Will be run with /x 512my $Extra_Header_Re = <<'REGEX'; 513 ^ 514 (?: \s+ todo \s+ ([\d \t]+) )? # optional todo set 515 (?: \s* \# \s* ([\w:]+\s?) (.*) )? # optional skip with optional reason 516REGEX 517 518sub _is_header { 519 my($self, $line) = @_; 520 521 if( my($max, $extra) = $line =~ /^1\.\.(\d+)(.*)/ ) { 522 $self->{max} = $max; 523 assert( $self->{max} >= 0, 'Max # of tests looks right' ); 524 525 if( defined $extra ) { 526 my($todo, $skip, $reason) = $extra =~ /$Extra_Header_Re/xo; 527 528 $self->{todo} = { map { $_ => 1 } split /\s+/, $todo } if $todo; 529 530 if( $self->{max} == 0 ) { 531 $reason = '' unless defined $skip and $skip =~ /^Skip/i; 532 } 533 534 $self->{skip_all} = $reason; 535 } 536 537 return $YES; 538 } 539 else { 540 return $NO; 541 } 542} 543 544=head2 C<_is_test> 545 546 my $is_test = $strap->_is_test($line, \%test); 547 548Checks if the $line is a test report (ie. 'ok/not ok'). Reports the 549result back in C<%test> which will contain: 550 551 ok did it succeed? This is the literal 'ok' or 'not ok'. 552 name name of the test (if any) 553 number test number (if any) 554 555 type 'todo' or 'skip' (if any) 556 reason why is it todo or skip? (if any) 557 558If will also catch lone 'not' lines, note it saw them 559C<< $strap->{saw_lone_not} >> and the line in C<< $strap->{lone_not_line} >>. 560 561=cut 562 563my $Report_Re = <<'REGEX'; 564 ^ 565 (not\ )? # failure? 566 ok\b 567 (?:\s+(\d+))? # optional test number 568 \s* 569 (.*) # and the rest 570REGEX 571 572my $Extra_Re = <<'REGEX'; 573 ^ 574 (.*?) (?:(?:[^\\]|^)# (.*))? 575 $ 576REGEX 577 578sub _is_test { 579 my($self, $line, $test) = @_; 580 581 # We pulverize the line down into pieces in three parts. 582 if( my($not, $num, $extra) = $line =~ /$Report_Re/ox ) { 583 my ($name, $control) = $extra ? split(/(?:[^\\]|^)#/, $extra) : (); 584 my ($type, $reason) = $control ? $control =~ /^\s*(\S+)(?:\s+(.*))?$/ : (); 585 586 $test->{number} = $num; 587 $test->{ok} = $not ? 0 : 1; 588 $test->{name} = $name; 589 590 if( defined $type ) { 591 $test->{type} = $type =~ /^TODO$/i ? 'todo' : 592 $type =~ /^Skip/i ? 'skip' : 0; 593 } 594 else { 595 $test->{type} = ''; 596 } 597 $test->{reason} = $reason; 598 599 return $YES; 600 } 601 else{ 602 # Sometimes the "not " and "ok" will be on separate lines on VMS. 603 # We catch this and remember we saw it. 604 if( $line =~ /^not\s+$/ ) { 605 $self->{saw_lone_not} = 1; 606 $self->{lone_not_line} = $self->{line}; 607 } 608 609 return $NO; 610 } 611} 612 613=head2 C<_is_bail_out> 614 615 my $is_bail_out = $strap->_is_bail_out($line, \$reason); 616 617Checks if the line is a "Bail out!". Places the reason for bailing 618(if any) in $reason. 619 620=cut 621 622sub _is_bail_out { 623 my($self, $line, $reason) = @_; 624 625 if( $line =~ /^Bail out!\s*(.*)/i ) { 626 $$reason = $1 if $1; 627 return $YES; 628 } 629 else { 630 return $NO; 631 } 632} 633 634=head2 C<_reset_file_state> 635 636 $strap->_reset_file_state; 637 638Resets things like C<< $strap->{max} >> , C<< $strap->{skip_all} >>, 639etc. so it's ready to parse the next file. 640 641=cut 642 643sub _reset_file_state { 644 my($self) = shift; 645 646 delete @{$self}{qw(max skip_all todo)}; 647 $self->{line} = 0; 648 $self->{saw_header} = 0; 649 $self->{saw_bailout}= 0; 650 $self->{saw_lone_not} = 0; 651 $self->{lone_not_line} = 0; 652 $self->{bailout_reason} = ''; 653 $self->{'next'} = 1; 654} 655 656=head1 Results 657 658The C<%results> returned from C<analyze()> contain the following 659information: 660 661 passing true if the whole test is considered a pass 662 (or skipped), false if its a failure 663 664 exit the exit code of the test run, if from a file 665 wait the wait code of the test run, if from a file 666 667 max total tests which should have been run 668 seen total tests actually seen 669 skip_all if the whole test was skipped, this will 670 contain the reason. 671 672 ok number of tests which passed 673 (including todo and skips) 674 675 todo number of todo tests seen 676 bonus number of todo tests which 677 unexpectedly passed 678 679 skip number of tests skipped 680 681So a successful test should have max == seen == ok. 682 683 684There is one final item, the details. 685 686 details an array ref reporting the result of 687 each test looks like this: 688 689 $results{details}[$test_num - 1] = 690 { ok => is the test considered ok? 691 actual_ok => did it literally say 'ok'? 692 name => name of the test (if any) 693 type => 'skip' or 'todo' (if any) 694 reason => reason for the above (if any) 695 }; 696 697Element 0 of the details is test #1. I tried it with element 1 being 698#1 and 0 being empty, this is less awkward. 699 700=head2 C<_detailize> 701 702 my %details = $strap->_detailize($pass, \%test); 703 704Generates the details based on the last test line seen. C<$pass> is 705true if it was considered to be a passed test. C<%test> is the results 706of the test you're summarizing. 707 708=cut 709 710sub _detailize { 711 my($self, $pass, $test) = @_; 712 713 my %details = ( ok => $pass, 714 actual_ok => $test->{ok} 715 ); 716 717 assert( !(grep !defined $details{$_}, keys %details), 718 'test contains the ok and actual_ok info' ); 719 720 # We don't want these to be undef because they are often 721 # checked and don't want the checker to have to deal with 722 # uninitialized vars. 723 foreach my $piece (qw(name type reason)) { 724 $details{$piece} = defined $test->{$piece} ? $test->{$piece} : ''; 725 } 726 727 return %details; 728} 729 730=head1 EXAMPLES 731 732See F<examples/mini_harness.plx> for an example of use. 733 734=head1 AUTHOR 735 736Michael G Schwern C<< <schwern@pobox.com> >>, currently maintained by 737Andy Lester C<< <andy@petdance.com> >>. 738 739=head1 SEE ALSO 740 741L<Test::Harness> 742 743=cut 744 7451; 746