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