home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 June / PCWorld_2005-06_cd.bin / software / vyzkuste / firewally / firewally.exe / framework-2.3.exe / Straps.pm < prev    next >
Text File  |  2003-11-07  |  16KB  |  668 lines

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