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